From 416affdb7cad96a884f1df7054b6407faff4ef7d Mon Sep 17 00:00:00 2001 From: AaryaGadekar Date: Tue, 21 Mar 2023 19:17:11 +0530 Subject: [PATCH] first commit --- __init__.py | 175 + lib/aiohttp-3.8.4.dist-info/INSTALLER | 1 + lib/aiohttp-3.8.4.dist-info/LICENSE.txt | 13 + lib/aiohttp-3.8.4.dist-info/METADATA | 253 + lib/aiohttp-3.8.4.dist-info/RECORD | 117 + lib/aiohttp-3.8.4.dist-info/WHEEL | 5 + lib/aiohttp-3.8.4.dist-info/top_level.txt | 1 + lib/aiohttp/.hash/_cparser.pxd.hash | 1 + lib/aiohttp/.hash/_find_header.pxd.hash | 1 + lib/aiohttp/.hash/_helpers.pyi.hash | 1 + lib/aiohttp/.hash/_helpers.pyx.hash | 1 + lib/aiohttp/.hash/_http_parser.pyx.hash | 1 + lib/aiohttp/.hash/_http_writer.pyx.hash | 1 + lib/aiohttp/.hash/_websocket.pyx.hash | 1 + lib/aiohttp/.hash/hdrs.py.hash | 1 + lib/aiohttp/__init__.py | 216 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 3679 bytes lib/aiohttp/__pycache__/abc.cpython-39.pyc | Bin 0 -> 8706 bytes .../__pycache__/base_protocol.cpython-39.pyc | Bin 0 -> 2811 bytes lib/aiohttp/__pycache__/client.cpython-39.pyc | Bin 0 -> 29354 bytes .../client_exceptions.cpython-39.pyc | Bin 0 -> 11256 bytes .../__pycache__/client_proto.cpython-39.pyc | Bin 0 -> 6154 bytes .../__pycache__/client_reqrep.cpython-39.pyc | Bin 0 -> 28022 bytes .../__pycache__/client_ws.cpython-39.pyc | Bin 0 -> 9041 bytes .../__pycache__/connector.cpython-39.pyc | Bin 0 -> 35696 bytes .../__pycache__/cookiejar.cpython-39.pyc | Bin 0 -> 10712 bytes .../__pycache__/formdata.cpython-39.pyc | Bin 0 -> 4529 bytes lib/aiohttp/__pycache__/hdrs.cpython-39.pyc | Bin 0 -> 5289 bytes .../__pycache__/helpers.cpython-39.pyc | Bin 0 -> 26239 bytes lib/aiohttp/__pycache__/http.cpython-39.pyc | Bin 0 -> 1316 bytes .../http_exceptions.cpython-39.pyc | Bin 0 -> 4271 bytes .../__pycache__/http_parser.cpython-39.pyc | Bin 0 -> 18022 bytes .../__pycache__/http_websocket.cpython-39.pyc | Bin 0 -> 15094 bytes .../__pycache__/http_writer.cpython-39.pyc | Bin 0 -> 5587 bytes lib/aiohttp/__pycache__/locks.cpython-39.pyc | Bin 0 -> 1639 bytes lib/aiohttp/__pycache__/log.cpython-39.pyc | Bin 0 -> 448 bytes .../__pycache__/multipart.cpython-39.pyc | Bin 0 -> 26294 bytes .../__pycache__/payload.cpython-39.pyc | Bin 0 -> 13523 bytes .../payload_streamer.cpython-39.pyc | Bin 0 -> 3245 bytes .../__pycache__/pytest_plugin.cpython-39.pyc | Bin 0 -> 9961 bytes .../__pycache__/resolver.cpython-39.pyc | Bin 0 -> 3966 bytes .../__pycache__/streams.cpython-39.pyc | Bin 0 -> 18606 bytes .../__pycache__/tcp_helpers.cpython-39.pyc | Bin 0 -> 1133 bytes .../__pycache__/test_utils.cpython-39.pyc | Bin 0 -> 21871 bytes .../__pycache__/tracing.cpython-39.pyc | Bin 0 -> 15032 bytes .../__pycache__/typedefs.cpython-39.pyc | Bin 0 -> 1503 bytes lib/aiohttp/__pycache__/web.cpython-39.pyc | Bin 0 -> 10092 bytes .../__pycache__/web_app.cpython-39.pyc | Bin 0 -> 15237 bytes .../__pycache__/web_exceptions.cpython-39.pyc | Bin 0 -> 12026 bytes .../web_fileresponse.cpython-39.pyc | Bin 0 -> 6021 bytes .../__pycache__/web_log.cpython-39.pyc | Bin 0 -> 7171 bytes .../web_middlewares.cpython-39.pyc | Bin 0 -> 3871 bytes .../__pycache__/web_protocol.cpython-39.pyc | Bin 0 -> 16505 bytes .../__pycache__/web_request.cpython-39.pyc | Bin 0 -> 23967 bytes .../__pycache__/web_response.cpython-39.pyc | Bin 0 -> 21119 bytes .../__pycache__/web_routedef.cpython-39.pyc | Bin 0 -> 7960 bytes .../__pycache__/web_runner.cpython-39.pyc | Bin 0 -> 11660 bytes .../__pycache__/web_server.cpython-39.pyc | Bin 0 -> 2760 bytes .../web_urldispatcher.cpython-39.pyc | Bin 0 -> 42111 bytes lib/aiohttp/__pycache__/web_ws.cpython-39.pyc | Bin 0 -> 13422 bytes lib/aiohttp/__pycache__/worker.cpython-39.pyc | Bin 0 -> 6985 bytes lib/aiohttp/_cparser.pxd | 190 + lib/aiohttp/_find_header.pxd | 2 + lib/aiohttp/_headers.pxi | 83 + lib/aiohttp/_helpers.cp39-win_amd64.pyd | Bin 0 -> 39936 bytes lib/aiohttp/_helpers.pyi | 6 + lib/aiohttp/_helpers.pyx | 35 + lib/aiohttp/_http_parser.cp39-win_amd64.pyd | Bin 0 -> 215552 bytes lib/aiohttp/_http_parser.pyx | 832 ++ lib/aiohttp/_http_writer.cp39-win_amd64.pyd | Bin 0 -> 35840 bytes lib/aiohttp/_http_writer.pyx | 163 + lib/aiohttp/_websocket.cp39-win_amd64.pyd | Bin 0 -> 24064 bytes lib/aiohttp/_websocket.pyx | 56 + lib/aiohttp/abc.py | 207 + lib/aiohttp/base_protocol.py | 90 + lib/aiohttp/client.py | 1305 +++ lib/aiohttp/client_exceptions.py | 342 + lib/aiohttp/client_proto.py | 251 + lib/aiohttp/client_reqrep.py | 1134 +++ lib/aiohttp/client_ws.py | 300 + lib/aiohttp/connector.py | 1453 +++ lib/aiohttp/cookiejar.py | 415 + lib/aiohttp/formdata.py | 172 + lib/aiohttp/hdrs.py | 114 + lib/aiohttp/helpers.py | 878 ++ lib/aiohttp/http.py | 70 + lib/aiohttp/http_exceptions.py | 105 + lib/aiohttp/http_parser.py | 969 ++ lib/aiohttp/http_websocket.py | 701 ++ lib/aiohttp/http_writer.py | 198 + lib/aiohttp/locks.py | 41 + lib/aiohttp/log.py | 8 + lib/aiohttp/multipart.py | 961 ++ lib/aiohttp/payload.py | 465 + lib/aiohttp/payload_streamer.py | 75 + lib/aiohttp/py.typed | 1 + lib/aiohttp/pytest_plugin.py | 391 + lib/aiohttp/resolver.py | 160 + lib/aiohttp/streams.py | 660 ++ lib/aiohttp/tcp_helpers.py | 37 + lib/aiohttp/test_utils.py | 706 ++ lib/aiohttp/tracing.py | 472 + lib/aiohttp/typedefs.py | 64 + lib/aiohttp/web.py | 588 ++ lib/aiohttp/web_app.py | 557 ++ lib/aiohttp/web_exceptions.py | 441 + lib/aiohttp/web_fileresponse.py | 288 + lib/aiohttp/web_log.py | 208 + lib/aiohttp/web_middlewares.py | 119 + lib/aiohttp/web_protocol.py | 679 ++ lib/aiohttp/web_request.py | 882 ++ lib/aiohttp/web_response.py | 825 ++ lib/aiohttp/web_routedef.py | 216 + lib/aiohttp/web_runner.py | 381 + lib/aiohttp/web_server.py | 62 + lib/aiohttp/web_urldispatcher.py | 1220 +++ lib/aiohttp/web_ws.py | 487 + lib/aiohttp/worker.py | 269 + lib/aiosignal-1.3.1.dist-info/INSTALLER | 1 + lib/aiosignal-1.3.1.dist-info/LICENSE | 201 + lib/aiosignal-1.3.1.dist-info/METADATA | 128 + lib/aiosignal-1.3.1.dist-info/RECORD | 10 + lib/aiosignal-1.3.1.dist-info/WHEEL | 5 + lib/aiosignal-1.3.1.dist-info/top_level.txt | 1 + lib/aiosignal/__init__.py | 36 + lib/aiosignal/__init__.pyi | 12 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1324 bytes lib/aiosignal/py.typed | 0 lib/async_timeout-4.0.2.dist-info/INSTALLER | 1 + lib/async_timeout-4.0.2.dist-info/LICENSE | 13 + lib/async_timeout-4.0.2.dist-info/METADATA | 133 + lib/async_timeout-4.0.2.dist-info/RECORD | 10 + lib/async_timeout-4.0.2.dist-info/WHEEL | 5 + .../top_level.txt | 1 + lib/async_timeout-4.0.2.dist-info/zip-safe | 1 + lib/async_timeout/__init__.py | 247 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 6854 bytes lib/async_timeout/py.typed | 1 + lib/attr/__init__.py | 93 + lib/attr/__init__.pyi | 509 + lib/attr/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 2025 bytes lib/attr/__pycache__/_cmp.cpython-39.pyc | Bin 0 -> 3756 bytes lib/attr/__pycache__/_compat.cpython-39.pyc | Bin 0 -> 4117 bytes lib/attr/__pycache__/_config.cpython-39.pyc | Bin 0 -> 985 bytes lib/attr/__pycache__/_funcs.cpython-39.pyc | Bin 0 -> 10216 bytes lib/attr/__pycache__/_make.cpython-39.pyc | Bin 0 -> 71835 bytes lib/attr/__pycache__/_next_gen.cpython-39.pyc | Bin 0 -> 5090 bytes .../__pycache__/_version_info.cpython-39.pyc | Bin 0 -> 2303 bytes .../__pycache__/converters.cpython-39.pyc | Bin 0 -> 3509 bytes .../__pycache__/exceptions.cpython-39.pyc | Bin 0 -> 3137 bytes lib/attr/__pycache__/filters.cpython-39.pyc | Bin 0 -> 1604 bytes lib/attr/__pycache__/setters.cpython-39.pyc | Bin 0 -> 1509 bytes .../__pycache__/validators.cpython-39.pyc | Bin 0 -> 20165 bytes lib/attr/_cmp.py | 155 + lib/attr/_cmp.pyi | 13 + lib/attr/_compat.py | 176 + lib/attr/_config.py | 31 + lib/attr/_funcs.py | 418 + lib/attr/_make.py | 2965 ++++++ lib/attr/_next_gen.py | 226 + lib/attr/_typing_compat.pyi | 15 + lib/attr/_version_info.py | 86 + lib/attr/_version_info.pyi | 9 + lib/attr/converters.py | 144 + lib/attr/converters.pyi | 13 + lib/attr/exceptions.py | 92 + lib/attr/exceptions.pyi | 17 + lib/attr/filters.py | 51 + lib/attr/filters.pyi | 6 + lib/attr/py.typed | 0 lib/attr/setters.py | 73 + lib/attr/setters.pyi | 19 + lib/attr/validators.py | 714 ++ lib/attr/validators.pyi | 86 + lib/attrs-22.2.0.dist-info/INSTALLER | 1 + lib/attrs-22.2.0.dist-info/LICENSE | 21 + lib/attrs-22.2.0.dist-info/METADATA | 278 + lib/attrs-22.2.0.dist-info/RECORD | 56 + lib/attrs-22.2.0.dist-info/WHEEL | 5 + lib/attrs-22.2.0.dist-info/top_level.txt | 2 + lib/attrs/__init__.py | 72 + lib/attrs/__init__.pyi | 67 + lib/attrs/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1092 bytes .../__pycache__/converters.cpython-39.pyc | Bin 0 -> 200 bytes .../__pycache__/exceptions.cpython-39.pyc | Bin 0 -> 200 bytes lib/attrs/__pycache__/filters.cpython-39.pyc | Bin 0 -> 194 bytes lib/attrs/__pycache__/setters.cpython-39.pyc | Bin 0 -> 194 bytes .../__pycache__/validators.cpython-39.pyc | Bin 0 -> 200 bytes lib/attrs/converters.py | 3 + lib/attrs/exceptions.py | 3 + lib/attrs/filters.py | 3 + lib/attrs/py.typed | 0 lib/attrs/setters.py | 3 + lib/attrs/validators.py | 3 + lib/certifi-2022.12.7.dist-info/INSTALLER | 1 + lib/certifi-2022.12.7.dist-info/LICENSE | 21 + lib/certifi-2022.12.7.dist-info/METADATA | 83 + lib/certifi-2022.12.7.dist-info/RECORD | 14 + lib/certifi-2022.12.7.dist-info/WHEEL | 5 + lib/certifi-2022.12.7.dist-info/top_level.txt | 1 + lib/certifi/__init__.py | 4 + lib/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 281 bytes .../__pycache__/__main__.cpython-39.pyc | Bin 0 -> 417 bytes lib/certifi/__pycache__/core.cpython-39.pyc | Bin 0 -> 1888 bytes lib/certifi/cacert.pem | 4527 +++++++++ lib/certifi/core.py | 108 + lib/certifi/py.typed | 0 .../INSTALLER | 1 + .../LICENSE | 21 + .../METADATA | 616 ++ lib/charset_normalizer-3.1.0.dist-info/RECORD | 35 + lib/charset_normalizer-3.1.0.dist-info/WHEEL | 5 + .../entry_points.txt | 2 + .../top_level.txt | 1 + lib/charset_normalizer/__init__.py | 45 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1552 bytes .../__pycache__/api.cpython-39.pyc | Bin 0 -> 10378 bytes .../__pycache__/cd.cpython-39.pyc | Bin 0 -> 9670 bytes .../__pycache__/constant.cpython-39.pyc | Bin 0 -> 13958 bytes .../__pycache__/legacy.cpython-39.pyc | Bin 0 -> 1829 bytes .../__pycache__/md.cpython-39.pyc | Bin 0 -> 15121 bytes .../__pycache__/models.cpython-39.pyc | Bin 0 -> 11438 bytes .../__pycache__/utils.cpython-39.pyc | Bin 0 -> 9013 bytes .../__pycache__/version.cpython-39.pyc | Bin 0 -> 263 bytes lib/charset_normalizer/api.py | 554 ++ lib/charset_normalizer/assets/__init__.py | 1440 +++ .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 9549 bytes lib/charset_normalizer/cd.py | 390 + lib/charset_normalizer/cli/__init__.py | 0 .../cli/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 183 bytes .../cli/__pycache__/normalizer.cpython-39.pyc | Bin 0 -> 6482 bytes lib/charset_normalizer/cli/normalizer.py | 296 + lib/charset_normalizer/constant.py | 495 + lib/charset_normalizer/legacy.py | 54 + lib/charset_normalizer/md.cp39-win_amd64.pyd | Bin 0 -> 10752 bytes lib/charset_normalizer/md.py | 571 ++ .../md__mypyc.cp39-win_amd64.pyd | Bin 0 -> 116736 bytes lib/charset_normalizer/models.py | 337 + lib/charset_normalizer/py.typed | 0 lib/charset_normalizer/utils.py | 414 + lib/charset_normalizer/version.py | 6 + lib/colorama-0.4.6.dist-info/INSTALLER | 1 + lib/colorama-0.4.6.dist-info/METADATA | 441 + lib/colorama-0.4.6.dist-info/RECORD | 31 + lib/colorama-0.4.6.dist-info/WHEEL | 5 + .../licenses/LICENSE.txt | 27 + lib/colorama/__init__.py | 7 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 455 bytes lib/colorama/__pycache__/ansi.cpython-39.pyc | Bin 0 -> 3205 bytes .../__pycache__/ansitowin32.cpython-39.pyc | Bin 0 -> 8283 bytes .../__pycache__/initialise.cpython-39.pyc | Bin 0 -> 2256 bytes lib/colorama/__pycache__/win32.cpython-39.pyc | Bin 0 -> 4438 bytes .../__pycache__/winterm.cpython-39.pyc | Bin 0 -> 5241 bytes lib/colorama/ansi.py | 102 + lib/colorama/ansitowin32.py | 277 + lib/colorama/initialise.py | 121 + lib/colorama/tests/__init__.py | 1 + .../tests/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 175 bytes .../__pycache__/ansi_test.cpython-39.pyc | Bin 0 -> 2535 bytes .../ansitowin32_test.cpython-39.pyc | Bin 0 -> 11538 bytes .../initialise_test.cpython-39.pyc | Bin 0 -> 7067 bytes .../__pycache__/isatty_test.cpython-39.pyc | Bin 0 -> 2788 bytes .../tests/__pycache__/utils.cpython-39.pyc | Bin 0 -> 1643 bytes .../__pycache__/winterm_test.cpython-39.pyc | Bin 0 -> 3310 bytes lib/colorama/tests/ansi_test.py | 76 + lib/colorama/tests/ansitowin32_test.py | 294 + lib/colorama/tests/initialise_test.py | 189 + lib/colorama/tests/isatty_test.py | 57 + lib/colorama/tests/utils.py | 49 + lib/colorama/tests/winterm_test.py | 131 + lib/colorama/win32.py | 180 + lib/colorama/winterm.py | 195 + lib/frozenlist-1.3.3.dist-info/INSTALLER | 1 + lib/frozenlist-1.3.3.dist-info/LICENSE | 201 + lib/frozenlist-1.3.3.dist-info/METADATA | 150 + lib/frozenlist-1.3.3.dist-info/RECORD | 12 + lib/frozenlist-1.3.3.dist-info/WHEEL | 5 + lib/frozenlist-1.3.3.dist-info/top_level.txt | 1 + lib/frozenlist/__init__.py | 96 + lib/frozenlist/__init__.pyi | 47 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 3274 bytes lib/frozenlist/_frozenlist.cp39-win_amd64.pyd | Bin 0 -> 53760 bytes lib/frozenlist/_frozenlist.pyx | 123 + lib/frozenlist/py.typed | 1 + lib/idna-3.4.dist-info/INSTALLER | 1 + lib/idna-3.4.dist-info/LICENSE.md | 29 + lib/idna-3.4.dist-info/METADATA | 242 + lib/idna-3.4.dist-info/RECORD | 22 + lib/idna-3.4.dist-info/WHEEL | 4 + lib/idna/__init__.py | 44 + lib/idna/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 824 bytes lib/idna/__pycache__/codec.cpython-39.pyc | Bin 0 -> 3061 bytes lib/idna/__pycache__/compat.cpython-39.pyc | Bin 0 -> 743 bytes lib/idna/__pycache__/core.cpython-39.pyc | Bin 0 -> 9855 bytes lib/idna/__pycache__/idnadata.cpython-39.pyc | Bin 0 -> 23178 bytes lib/idna/__pycache__/intranges.cpython-39.pyc | Bin 0 -> 1974 bytes .../__pycache__/package_data.cpython-39.pyc | Bin 0 -> 188 bytes lib/idna/__pycache__/uts46data.cpython-39.pyc | Bin 0 -> 153183 bytes lib/idna/codec.py | 112 + lib/idna/compat.py | 13 + lib/idna/core.py | 400 + lib/idna/idnadata.py | 2151 +++++ lib/idna/intranges.py | 54 + lib/idna/package_data.py | 2 + lib/idna/py.typed | 0 lib/idna/uts46data.py | 8600 +++++++++++++++++ lib/multidict-6.0.4.dist-info/INSTALLER | 1 + lib/multidict-6.0.4.dist-info/LICENSE | 13 + lib/multidict-6.0.4.dist-info/METADATA | 130 + lib/multidict-6.0.4.dist-info/RECORD | 19 + lib/multidict-6.0.4.dist-info/WHEEL | 5 + lib/multidict-6.0.4.dist-info/top_level.txt | 1 + lib/multidict/__init__.py | 48 + lib/multidict/__init__.pyi | 150 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 845 bytes lib/multidict/__pycache__/_abc.cpython-39.pyc | Bin 0 -> 1979 bytes .../__pycache__/_compat.cpython-39.pyc | Bin 0 -> 454 bytes .../_multidict_base.cpython-39.pyc | Bin 0 -> 3445 bytes .../__pycache__/_multidict_py.cpython-39.pyc | Bin 0 -> 16881 bytes lib/multidict/_abc.py | 48 + lib/multidict/_compat.py | 14 + lib/multidict/_multidict.cp39-win_amd64.pyd | Bin 0 -> 46592 bytes lib/multidict/_multidict_base.py | 144 + lib/multidict/_multidict_py.py | 526 + lib/multidict/py.typed | 1 + lib/openai-0.27.2.dist-info/INSTALLER | 1 + lib/openai-0.27.2.dist-info/LICENSE | 21 + lib/openai-0.27.2.dist-info/METADATA | 338 + lib/openai-0.27.2.dist-info/RECORD | 111 + lib/openai-0.27.2.dist-info/REQUESTED | 0 lib/openai-0.27.2.dist-info/WHEEL | 5 + lib/openai-0.27.2.dist-info/entry_points.txt | 2 + lib/openai-0.27.2.dist-info/top_level.txt | 1 + lib/openai-0.27.2.dist-info/zip-safe | 1 + lib/openai/__init__.py | 86 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1502 bytes .../_openai_scripts.cpython-39.pyc | Bin 0 -> 2034 bytes .../__pycache__/api_requestor.cpython-39.pyc | Bin 0 -> 15374 bytes lib/openai/__pycache__/cli.cpython-39.pyc | Bin 0 -> 29206 bytes lib/openai/__pycache__/datalib.cpython-39.pyc | Bin 0 -> 1706 bytes .../embeddings_utils.cpython-39.pyc | Bin 0 -> 8734 bytes lib/openai/__pycache__/error.cpython-39.pyc | Bin 0 -> 4794 bytes .../__pycache__/object_classes.cpython-39.pyc | Bin 0 -> 488 bytes .../__pycache__/openai_object.cpython-39.pyc | Bin 0 -> 7667 bytes .../openai_response.cpython-39.pyc | Bin 0 -> 1149 bytes .../upload_progress.cpython-39.pyc | Bin 0 -> 2022 bytes lib/openai/__pycache__/util.cpython-39.pyc | Bin 0 -> 5416 bytes .../__pycache__/validators.cpython-39.pyc | Bin 0 -> 27947 bytes lib/openai/__pycache__/version.cpython-39.pyc | Bin 0 -> 184 bytes .../__pycache__/wandb_logger.cpython-39.pyc | Bin 0 -> 7325 bytes lib/openai/_openai_scripts.py | 74 + lib/openai/api_requestor.py | 695 ++ lib/openai/api_resources/__init__.py | 14 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1046 bytes .../__pycache__/audio.cpython-39.pyc | Bin 0 -> 3408 bytes .../chat_completion.cpython-39.pyc | Bin 0 -> 1537 bytes .../__pycache__/completion.cpython-39.pyc | Bin 0 -> 1583 bytes .../__pycache__/customer.cpython-39.pyc | Bin 0 -> 929 bytes .../__pycache__/deployment.cpython-39.pyc | Bin 0 -> 3589 bytes .../__pycache__/edit.cpython-39.pyc | Bin 0 -> 1700 bytes .../__pycache__/embedding.cpython-39.pyc | Bin 0 -> 1999 bytes .../__pycache__/engine.cpython-39.pyc | Bin 0 -> 1607 bytes .../__pycache__/error_object.cpython-39.pyc | Bin 0 -> 889 bytes .../__pycache__/file.cpython-39.pyc | Bin 0 -> 4705 bytes .../__pycache__/fine_tune.cpython-39.pyc | Bin 0 -> 3652 bytes .../__pycache__/image.cpython-39.pyc | Bin 0 -> 3619 bytes .../__pycache__/model.cpython-39.pyc | Bin 0 -> 463 bytes .../__pycache__/moderation.cpython-39.pyc | Bin 0 -> 1597 bytes lib/openai/api_resources/abstract/__init__.py | 10 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 771 bytes .../__pycache__/api_resource.cpython-39.pyc | Bin 0 -> 3805 bytes .../createable_api_resource.cpython-39.pyc | Bin 0 -> 1952 bytes .../deletable_api_resource.cpython-39.pyc | Bin 0 -> 1772 bytes .../engine_api_resource.cpython-39.pyc | Bin 0 -> 5942 bytes .../listable_api_resource.cpython-39.pyc | Bin 0 -> 2122 bytes ...sted_resource_class_methods.cpython-39.pyc | Bin 0 -> 4061 bytes .../updateable_api_resource.cpython-39.pyc | Bin 0 -> 1014 bytes .../api_resources/abstract/api_resource.py | 172 + .../abstract/createable_api_resource.py | 98 + .../abstract/deletable_api_resource.py | 48 + .../abstract/engine_api_resource.py | 325 + .../abstract/listable_api_resource.py | 95 + .../abstract/nested_resource_class_methods.py | 154 + .../abstract/updateable_api_resource.py | 16 + lib/openai/api_resources/audio.py | 205 + lib/openai/api_resources/chat_completion.py | 50 + lib/openai/api_resources/completion.py | 50 + lib/openai/api_resources/customer.py | 17 + lib/openai/api_resources/deployment.py | 119 + lib/openai/api_resources/edit.py | 57 + lib/openai/api_resources/embedding.py | 91 + lib/openai/api_resources/engine.py | 50 + lib/openai/api_resources/error_object.py | 28 + .../api_resources/experimental/__init__.py | 3 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 286 bytes .../completion_config.cpython-39.pyc | Bin 0 -> 562 bytes .../experimental/completion_config.py | 11 + lib/openai/api_resources/file.py | 261 + lib/openai/api_resources/fine_tune.py | 204 + lib/openai/api_resources/image.py | 242 + lib/openai/api_resources/model.py | 5 + lib/openai/api_resources/moderation.py | 45 + lib/openai/cli.py | 1116 +++ lib/openai/datalib.py | 56 + lib/openai/embeddings_utils.py | 254 + lib/openai/error.py | 168 + lib/openai/object_classes.py | 11 + lib/openai/openai_object.py | 347 + lib/openai/openai_response.py | 20 + lib/openai/py.typed | 0 lib/openai/tests/__init__.py | 0 .../tests/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 173 bytes .../test_api_requestor.cpython-39.pyc | Bin 0 -> 2149 bytes .../__pycache__/test_endpoints.cpython-39.pyc | Bin 0 -> 6599 bytes .../test_exceptions.cpython-39.pyc | Bin 0 -> 1324 bytes .../__pycache__/test_file_cli.cpython-39.pyc | Bin 0 -> 1340 bytes ...est_long_examples_validator.cpython-39.pyc | Bin 0 -> 1501 bytes .../test_url_composition.cpython-39.pyc | Bin 0 -> 6372 bytes .../__pycache__/test_util.cpython-39.pyc | Bin 0 -> 1152 bytes lib/openai/tests/asyncio/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 181 bytes .../__pycache__/test_endpoints.cpython-39.pyc | Bin 0 -> 6667 bytes lib/openai/tests/asyncio/test_endpoints.py | 90 + lib/openai/tests/test_api_requestor.py | 69 + lib/openai/tests/test_endpoints.py | 88 + lib/openai/tests/test_exceptions.py | 40 + lib/openai/tests/test_file_cli.py | 39 + .../tests/test_long_examples_validator.py | 58 + lib/openai/tests/test_url_composition.py | 209 + lib/openai/tests/test_util.py | 30 + lib/openai/upload_progress.py | 52 + lib/openai/util.py | 188 + lib/openai/validators.py | 841 ++ lib/openai/version.py | 1 + lib/openai/wandb_logger.py | 300 + lib/requests-2.28.2.dist-info/INSTALLER | 1 + lib/requests-2.28.2.dist-info/LICENSE | 175 + lib/requests-2.28.2.dist-info/METADATA | 121 + lib/requests-2.28.2.dist-info/RECORD | 42 + lib/requests-2.28.2.dist-info/WHEEL | 5 + lib/requests-2.28.2.dist-info/top_level.txt | 1 + lib/requests/__init__.py | 180 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 3854 bytes .../__pycache__/__version__.cpython-39.pyc | Bin 0 -> 527 bytes .../_internal_utils.cpython-39.pyc | Bin 0 -> 1541 bytes .../__pycache__/adapters.cpython-39.pyc | Bin 0 -> 16863 bytes lib/requests/__pycache__/api.cpython-39.pyc | Bin 0 -> 6696 bytes lib/requests/__pycache__/auth.cpython-39.pyc | Bin 0 -> 8333 bytes lib/requests/__pycache__/certs.cpython-39.pyc | Bin 0 -> 598 bytes .../__pycache__/compat.cpython-39.pyc | Bin 0 -> 1497 bytes .../__pycache__/cookies.cpython-39.pyc | Bin 0 -> 18795 bytes .../__pycache__/exceptions.cpython-39.pyc | Bin 0 -> 6096 bytes lib/requests/__pycache__/help.cpython-39.pyc | Bin 0 -> 2810 bytes lib/requests/__pycache__/hooks.cpython-39.pyc | Bin 0 -> 967 bytes .../__pycache__/models.cpython-39.pyc | Bin 0 -> 24215 bytes .../__pycache__/packages.cpython-39.pyc | Bin 0 -> 688 bytes .../__pycache__/sessions.cpython-39.pyc | Bin 0 -> 19608 bytes .../__pycache__/status_codes.cpython-39.pyc | Bin 0 -> 4212 bytes .../__pycache__/structures.cpython-39.pyc | Bin 0 -> 4424 bytes lib/requests/__pycache__/utils.cpython-39.pyc | Bin 0 -> 24196 bytes lib/requests/__version__.py | 14 + lib/requests/_internal_utils.py | 48 + lib/requests/adapters.py | 584 ++ lib/requests/api.py | 157 + lib/requests/auth.py | 315 + lib/requests/certs.py | 17 + lib/requests/compat.py | 79 + lib/requests/cookies.py | 561 ++ lib/requests/exceptions.py | 141 + lib/requests/help.py | 134 + lib/requests/hooks.py | 33 + lib/requests/models.py | 1034 ++ lib/requests/packages.py | 28 + lib/requests/sessions.py | 831 ++ lib/requests/status_codes.py | 128 + lib/requests/structures.py | 99 + lib/requests/utils.py | 1086 +++ lib/tqdm-4.65.0.dist-info/INSTALLER | 1 + lib/tqdm-4.65.0.dist-info/LICENCE | 49 + lib/tqdm-4.65.0.dist-info/METADATA | 1581 +++ lib/tqdm-4.65.0.dist-info/RECORD | 74 + lib/tqdm-4.65.0.dist-info/WHEEL | 5 + lib/tqdm-4.65.0.dist-info/entry_points.txt | 2 + lib/tqdm-4.65.0.dist-info/top_level.txt | 1 + lib/tqdm/__init__.py | 38 + lib/tqdm/__main__.py | 3 + lib/tqdm/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1534 bytes lib/tqdm/__pycache__/__main__.cpython-39.pyc | Bin 0 -> 205 bytes lib/tqdm/__pycache__/_dist_ver.cpython-39.pyc | Bin 0 -> 188 bytes lib/tqdm/__pycache__/_main.cpython-39.pyc | Bin 0 -> 439 bytes lib/tqdm/__pycache__/_monitor.cpython-39.pyc | Bin 0 -> 2792 bytes lib/tqdm/__pycache__/_tqdm.cpython-39.pyc | Bin 0 -> 434 bytes lib/tqdm/__pycache__/_tqdm_gui.cpython-39.pyc | Bin 0 -> 447 bytes .../__pycache__/_tqdm_notebook.cpython-39.pyc | Bin 0 -> 467 bytes .../__pycache__/_tqdm_pandas.cpython-39.pyc | Bin 0 -> 948 bytes lib/tqdm/__pycache__/_utils.cpython-39.pyc | Bin 0 -> 820 bytes lib/tqdm/__pycache__/asyncio.cpython-39.pyc | Bin 0 -> 3338 bytes lib/tqdm/__pycache__/auto.cpython-39.pyc | Bin 0 -> 1109 bytes .../__pycache__/autonotebook.cpython-39.pyc | Bin 0 -> 997 bytes lib/tqdm/__pycache__/cli.cpython-39.pyc | Bin 0 -> 9079 bytes lib/tqdm/__pycache__/dask.cpython-39.pyc | Bin 0 -> 2071 bytes lib/tqdm/__pycache__/gui.cpython-39.pyc | Bin 0 -> 4533 bytes lib/tqdm/__pycache__/keras.cpython-39.pyc | Bin 0 -> 4907 bytes lib/tqdm/__pycache__/notebook.cpython-39.pyc | Bin 0 -> 7394 bytes lib/tqdm/__pycache__/rich.cpython-39.pyc | Bin 0 -> 5056 bytes lib/tqdm/__pycache__/std.cpython-39.pyc | Bin 0 -> 45371 bytes lib/tqdm/__pycache__/tk.cpython-39.pyc | Bin 0 -> 6110 bytes lib/tqdm/__pycache__/utils.cpython-39.pyc | Bin 0 -> 11534 bytes lib/tqdm/__pycache__/version.cpython-39.pyc | Bin 0 -> 499 bytes lib/tqdm/_dist_ver.py | 1 + lib/tqdm/_main.py | 9 + lib/tqdm/_monitor.py | 95 + lib/tqdm/_tqdm.py | 9 + lib/tqdm/_tqdm_gui.py | 9 + lib/tqdm/_tqdm_notebook.py | 9 + lib/tqdm/_tqdm_pandas.py | 24 + lib/tqdm/_utils.py | 11 + lib/tqdm/asyncio.py | 93 + lib/tqdm/auto.py | 40 + lib/tqdm/autonotebook.py | 29 + lib/tqdm/cli.py | 311 + lib/tqdm/completion.sh | 19 + lib/tqdm/contrib/__init__.py | 92 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 2988 bytes .../contrib/__pycache__/bells.cpython-39.pyc | Bin 0 -> 1027 bytes .../__pycache__/concurrent.cpython-39.pyc | Bin 0 -> 3609 bytes .../__pycache__/discord.cpython-39.pyc | Bin 0 -> 4297 bytes .../__pycache__/itertools.cpython-39.pyc | Bin 0 -> 938 bytes .../__pycache__/logging.cpython-39.pyc | Bin 0 -> 3859 bytes .../contrib/__pycache__/slack.cpython-39.pyc | Bin 0 -> 4331 bytes .../__pycache__/telegram.cpython-39.pyc | Bin 0 -> 5278 bytes .../__pycache__/utils_worker.cpython-39.pyc | Bin 0 -> 1475 bytes lib/tqdm/contrib/bells.py | 26 + lib/tqdm/contrib/concurrent.py | 105 + lib/tqdm/contrib/discord.py | 119 + lib/tqdm/contrib/itertools.py | 35 + lib/tqdm/contrib/logging.py | 126 + lib/tqdm/contrib/slack.py | 120 + lib/tqdm/contrib/telegram.py | 153 + lib/tqdm/contrib/utils_worker.py | 38 + lib/tqdm/dask.py | 44 + lib/tqdm/gui.py | 186 + lib/tqdm/keras.py | 122 + lib/tqdm/notebook.py | 321 + lib/tqdm/rich.py | 150 + lib/tqdm/std.py | 1521 +++ lib/tqdm/tk.py | 196 + lib/tqdm/tqdm.1 | 316 + lib/tqdm/utils.py | 330 + lib/tqdm/version.py | 9 + lib/urllib3-1.26.15.dist-info/INSTALLER | 1 + lib/urllib3-1.26.15.dist-info/LICENSE.txt | 21 + lib/urllib3-1.26.15.dist-info/METADATA | 1472 +++ lib/urllib3-1.26.15.dist-info/RECORD | 82 + lib/urllib3-1.26.15.dist-info/WHEEL | 6 + lib/urllib3-1.26.15.dist-info/top_level.txt | 1 + lib/urllib3/__init__.py | 102 + .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 2484 bytes .../__pycache__/_collections.cpython-39.pyc | Bin 0 -> 10761 bytes .../__pycache__/_version.cpython-39.pyc | Bin 0 -> 191 bytes .../__pycache__/connection.cpython-39.pyc | Bin 0 -> 13720 bytes .../__pycache__/connectionpool.cpython-39.pyc | Bin 0 -> 25398 bytes .../__pycache__/exceptions.cpython-39.pyc | Bin 0 -> 11623 bytes lib/urllib3/__pycache__/fields.cpython-39.pyc | Bin 0 -> 8138 bytes .../__pycache__/filepost.cpython-39.pyc | Bin 0 -> 2739 bytes .../__pycache__/poolmanager.cpython-39.pyc | Bin 0 -> 15158 bytes .../__pycache__/request.cpython-39.pyc | Bin 0 -> 5602 bytes .../__pycache__/response.cpython-39.pyc | Bin 0 -> 22471 bytes lib/urllib3/_collections.py | 337 + lib/urllib3/_version.py | 2 + lib/urllib3/connection.py | 572 ++ lib/urllib3/connectionpool.py | 1110 +++ lib/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 176 bytes .../_appengine_environ.cpython-39.pyc | Bin 0 -> 1396 bytes .../__pycache__/appengine.cpython-39.pyc | Bin 0 -> 8226 bytes .../__pycache__/ntlmpool.cpython-39.pyc | Bin 0 -> 3592 bytes .../__pycache__/pyopenssl.cpython-39.pyc | Bin 0 -> 15820 bytes .../securetransport.cpython-39.pyc | Bin 0 -> 21863 bytes .../contrib/__pycache__/socks.cpython-39.pyc | Bin 0 -> 5612 bytes lib/urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 193 bytes .../__pycache__/bindings.cpython-39.pyc | Bin 0 -> 10685 bytes .../__pycache__/low_level.cpython-39.pyc | Bin 0 -> 9153 bytes .../contrib/_securetransport/bindings.py | 519 + .../contrib/_securetransport/low_level.py | 397 + lib/urllib3/contrib/appengine.py | 314 + lib/urllib3/contrib/ntlmpool.py | 130 + lib/urllib3/contrib/pyopenssl.py | 518 + lib/urllib3/contrib/securetransport.py | 921 ++ lib/urllib3/contrib/socks.py | 216 + lib/urllib3/exceptions.py | 323 + lib/urllib3/fields.py | 274 + lib/urllib3/filepost.py | 98 + lib/urllib3/packages/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 177 bytes .../packages/__pycache__/six.cpython-39.pyc | Bin 0 -> 27560 bytes lib/urllib3/packages/backports/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 187 bytes .../__pycache__/makefile.cpython-39.pyc | Bin 0 -> 1285 bytes lib/urllib3/packages/backports/makefile.py | 51 + lib/urllib3/packages/six.py | 1076 +++ lib/urllib3/poolmanager.py | 537 + lib/urllib3/request.py | 170 + lib/urllib3/response.py | 885 ++ lib/urllib3/util/__init__.py | 49 + .../util/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1086 bytes .../__pycache__/connection.cpython-39.pyc | Bin 0 -> 3418 bytes .../util/__pycache__/proxy.cpython-39.pyc | Bin 0 -> 1322 bytes .../util/__pycache__/queue.cpython-39.pyc | Bin 0 -> 1041 bytes .../util/__pycache__/request.cpython-39.pyc | Bin 0 -> 3479 bytes .../util/__pycache__/response.cpython-39.pyc | Bin 0 -> 2326 bytes .../util/__pycache__/retry.cpython-39.pyc | Bin 0 -> 16238 bytes .../util/__pycache__/ssl_.cpython-39.pyc | Bin 0 -> 11298 bytes .../ssl_match_hostname.cpython-39.pyc | Bin 0 -> 3240 bytes .../__pycache__/ssltransport.cpython-39.pyc | Bin 0 -> 7456 bytes .../util/__pycache__/timeout.cpython-39.pyc | Bin 0 -> 9127 bytes .../util/__pycache__/url.cpython-39.pyc | Bin 0 -> 10656 bytes .../util/__pycache__/wait.cpython-39.pyc | Bin 0 -> 3109 bytes lib/urllib3/util/connection.py | 149 + lib/urllib3/util/proxy.py | 57 + lib/urllib3/util/queue.py | 22 + lib/urllib3/util/request.py | 146 + lib/urllib3/util/response.py | 107 + lib/urllib3/util/retry.py | 620 ++ lib/urllib3/util/ssl_.py | 495 + lib/urllib3/util/ssl_match_hostname.py | 159 + lib/urllib3/util/ssltransport.py | 221 + lib/urllib3/util/timeout.py | 271 + lib/urllib3/util/url.py | 435 + lib/urllib3/util/wait.py | 152 + lib/yarl-1.8.2.dist-info/INSTALLER | 1 + lib/yarl-1.8.2.dist-info/LICENSE | 13 + lib/yarl-1.8.2.dist-info/METADATA | 873 ++ lib/yarl-1.8.2.dist-info/RECORD | 19 + lib/yarl-1.8.2.dist-info/WHEEL | 5 + lib/yarl-1.8.2.dist-info/top_level.txt | 1 + lib/yarl/__init__.py | 5 + lib/yarl/__init__.pyi | 118 + lib/yarl/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 307 bytes lib/yarl/__pycache__/_quoting.cpython-39.pyc | Bin 0 -> 499 bytes .../__pycache__/_quoting_py.cpython-39.pyc | Bin 0 -> 4298 bytes lib/yarl/__pycache__/_url.cpython-39.pyc | Bin 0 -> 28027 bytes lib/yarl/_quoting.py | 18 + lib/yarl/_quoting_c.cp39-win_amd64.pyd | Bin 0 -> 68608 bytes lib/yarl/_quoting_c.pyi | 16 + lib/yarl/_quoting_c.pyx | 371 + lib/yarl/_quoting_py.py | 197 + lib/yarl/_url.py | 1159 +++ lib/yarl/py.typed | 1 + requirements.txt | 1 + 653 files changed, 90781 insertions(+) create mode 100644 __init__.py create mode 100644 lib/aiohttp-3.8.4.dist-info/INSTALLER create mode 100644 lib/aiohttp-3.8.4.dist-info/LICENSE.txt create mode 100644 lib/aiohttp-3.8.4.dist-info/METADATA create mode 100644 lib/aiohttp-3.8.4.dist-info/RECORD create mode 100644 lib/aiohttp-3.8.4.dist-info/WHEEL create mode 100644 lib/aiohttp-3.8.4.dist-info/top_level.txt create mode 100644 lib/aiohttp/.hash/_cparser.pxd.hash create mode 100644 lib/aiohttp/.hash/_find_header.pxd.hash create mode 100644 lib/aiohttp/.hash/_helpers.pyi.hash create mode 100644 lib/aiohttp/.hash/_helpers.pyx.hash create mode 100644 lib/aiohttp/.hash/_http_parser.pyx.hash create mode 100644 lib/aiohttp/.hash/_http_writer.pyx.hash create mode 100644 lib/aiohttp/.hash/_websocket.pyx.hash create mode 100644 lib/aiohttp/.hash/hdrs.py.hash create mode 100644 lib/aiohttp/__init__.py create mode 100644 lib/aiohttp/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/abc.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/base_protocol.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/client.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/client_exceptions.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/client_proto.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/client_reqrep.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/client_ws.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/connector.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/cookiejar.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/formdata.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/hdrs.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/helpers.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/http.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/http_exceptions.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/http_parser.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/http_websocket.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/http_writer.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/locks.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/log.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/multipart.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/payload.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/payload_streamer.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/pytest_plugin.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/resolver.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/streams.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/tcp_helpers.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/test_utils.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/tracing.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/typedefs.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_app.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_exceptions.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_fileresponse.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_log.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_middlewares.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_protocol.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_request.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_response.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_routedef.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_runner.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_server.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_urldispatcher.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/web_ws.cpython-39.pyc create mode 100644 lib/aiohttp/__pycache__/worker.cpython-39.pyc create mode 100644 lib/aiohttp/_cparser.pxd create mode 100644 lib/aiohttp/_find_header.pxd create mode 100644 lib/aiohttp/_headers.pxi create mode 100644 lib/aiohttp/_helpers.cp39-win_amd64.pyd create mode 100644 lib/aiohttp/_helpers.pyi create mode 100644 lib/aiohttp/_helpers.pyx create mode 100644 lib/aiohttp/_http_parser.cp39-win_amd64.pyd create mode 100644 lib/aiohttp/_http_parser.pyx create mode 100644 lib/aiohttp/_http_writer.cp39-win_amd64.pyd create mode 100644 lib/aiohttp/_http_writer.pyx create mode 100644 lib/aiohttp/_websocket.cp39-win_amd64.pyd create mode 100644 lib/aiohttp/_websocket.pyx create mode 100644 lib/aiohttp/abc.py create mode 100644 lib/aiohttp/base_protocol.py create mode 100644 lib/aiohttp/client.py create mode 100644 lib/aiohttp/client_exceptions.py create mode 100644 lib/aiohttp/client_proto.py create mode 100644 lib/aiohttp/client_reqrep.py create mode 100644 lib/aiohttp/client_ws.py create mode 100644 lib/aiohttp/connector.py create mode 100644 lib/aiohttp/cookiejar.py create mode 100644 lib/aiohttp/formdata.py create mode 100644 lib/aiohttp/hdrs.py create mode 100644 lib/aiohttp/helpers.py create mode 100644 lib/aiohttp/http.py create mode 100644 lib/aiohttp/http_exceptions.py create mode 100644 lib/aiohttp/http_parser.py create mode 100644 lib/aiohttp/http_websocket.py create mode 100644 lib/aiohttp/http_writer.py create mode 100644 lib/aiohttp/locks.py create mode 100644 lib/aiohttp/log.py create mode 100644 lib/aiohttp/multipart.py create mode 100644 lib/aiohttp/payload.py create mode 100644 lib/aiohttp/payload_streamer.py create mode 100644 lib/aiohttp/py.typed create mode 100644 lib/aiohttp/pytest_plugin.py create mode 100644 lib/aiohttp/resolver.py create mode 100644 lib/aiohttp/streams.py create mode 100644 lib/aiohttp/tcp_helpers.py create mode 100644 lib/aiohttp/test_utils.py create mode 100644 lib/aiohttp/tracing.py create mode 100644 lib/aiohttp/typedefs.py create mode 100644 lib/aiohttp/web.py create mode 100644 lib/aiohttp/web_app.py create mode 100644 lib/aiohttp/web_exceptions.py create mode 100644 lib/aiohttp/web_fileresponse.py create mode 100644 lib/aiohttp/web_log.py create mode 100644 lib/aiohttp/web_middlewares.py create mode 100644 lib/aiohttp/web_protocol.py create mode 100644 lib/aiohttp/web_request.py create mode 100644 lib/aiohttp/web_response.py create mode 100644 lib/aiohttp/web_routedef.py create mode 100644 lib/aiohttp/web_runner.py create mode 100644 lib/aiohttp/web_server.py create mode 100644 lib/aiohttp/web_urldispatcher.py create mode 100644 lib/aiohttp/web_ws.py create mode 100644 lib/aiohttp/worker.py create mode 100644 lib/aiosignal-1.3.1.dist-info/INSTALLER create mode 100644 lib/aiosignal-1.3.1.dist-info/LICENSE create mode 100644 lib/aiosignal-1.3.1.dist-info/METADATA create mode 100644 lib/aiosignal-1.3.1.dist-info/RECORD create mode 100644 lib/aiosignal-1.3.1.dist-info/WHEEL create mode 100644 lib/aiosignal-1.3.1.dist-info/top_level.txt create mode 100644 lib/aiosignal/__init__.py create mode 100644 lib/aiosignal/__init__.pyi create mode 100644 lib/aiosignal/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/aiosignal/py.typed create mode 100644 lib/async_timeout-4.0.2.dist-info/INSTALLER create mode 100644 lib/async_timeout-4.0.2.dist-info/LICENSE create mode 100644 lib/async_timeout-4.0.2.dist-info/METADATA create mode 100644 lib/async_timeout-4.0.2.dist-info/RECORD create mode 100644 lib/async_timeout-4.0.2.dist-info/WHEEL create mode 100644 lib/async_timeout-4.0.2.dist-info/top_level.txt create mode 100644 lib/async_timeout-4.0.2.dist-info/zip-safe create mode 100644 lib/async_timeout/__init__.py create mode 100644 lib/async_timeout/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/async_timeout/py.typed create mode 100644 lib/attr/__init__.py create mode 100644 lib/attr/__init__.pyi create mode 100644 lib/attr/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/attr/__pycache__/_cmp.cpython-39.pyc create mode 100644 lib/attr/__pycache__/_compat.cpython-39.pyc create mode 100644 lib/attr/__pycache__/_config.cpython-39.pyc create mode 100644 lib/attr/__pycache__/_funcs.cpython-39.pyc create mode 100644 lib/attr/__pycache__/_make.cpython-39.pyc create mode 100644 lib/attr/__pycache__/_next_gen.cpython-39.pyc create mode 100644 lib/attr/__pycache__/_version_info.cpython-39.pyc create mode 100644 lib/attr/__pycache__/converters.cpython-39.pyc create mode 100644 lib/attr/__pycache__/exceptions.cpython-39.pyc create mode 100644 lib/attr/__pycache__/filters.cpython-39.pyc create mode 100644 lib/attr/__pycache__/setters.cpython-39.pyc create mode 100644 lib/attr/__pycache__/validators.cpython-39.pyc create mode 100644 lib/attr/_cmp.py create mode 100644 lib/attr/_cmp.pyi create mode 100644 lib/attr/_compat.py create mode 100644 lib/attr/_config.py create mode 100644 lib/attr/_funcs.py create mode 100644 lib/attr/_make.py create mode 100644 lib/attr/_next_gen.py create mode 100644 lib/attr/_typing_compat.pyi create mode 100644 lib/attr/_version_info.py create mode 100644 lib/attr/_version_info.pyi create mode 100644 lib/attr/converters.py create mode 100644 lib/attr/converters.pyi create mode 100644 lib/attr/exceptions.py create mode 100644 lib/attr/exceptions.pyi create mode 100644 lib/attr/filters.py create mode 100644 lib/attr/filters.pyi create mode 100644 lib/attr/py.typed create mode 100644 lib/attr/setters.py create mode 100644 lib/attr/setters.pyi create mode 100644 lib/attr/validators.py create mode 100644 lib/attr/validators.pyi create mode 100644 lib/attrs-22.2.0.dist-info/INSTALLER create mode 100644 lib/attrs-22.2.0.dist-info/LICENSE create mode 100644 lib/attrs-22.2.0.dist-info/METADATA create mode 100644 lib/attrs-22.2.0.dist-info/RECORD create mode 100644 lib/attrs-22.2.0.dist-info/WHEEL create mode 100644 lib/attrs-22.2.0.dist-info/top_level.txt create mode 100644 lib/attrs/__init__.py create mode 100644 lib/attrs/__init__.pyi create mode 100644 lib/attrs/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/attrs/__pycache__/converters.cpython-39.pyc create mode 100644 lib/attrs/__pycache__/exceptions.cpython-39.pyc create mode 100644 lib/attrs/__pycache__/filters.cpython-39.pyc create mode 100644 lib/attrs/__pycache__/setters.cpython-39.pyc create mode 100644 lib/attrs/__pycache__/validators.cpython-39.pyc create mode 100644 lib/attrs/converters.py create mode 100644 lib/attrs/exceptions.py create mode 100644 lib/attrs/filters.py create mode 100644 lib/attrs/py.typed create mode 100644 lib/attrs/setters.py create mode 100644 lib/attrs/validators.py create mode 100644 lib/certifi-2022.12.7.dist-info/INSTALLER create mode 100644 lib/certifi-2022.12.7.dist-info/LICENSE create mode 100644 lib/certifi-2022.12.7.dist-info/METADATA create mode 100644 lib/certifi-2022.12.7.dist-info/RECORD create mode 100644 lib/certifi-2022.12.7.dist-info/WHEEL create mode 100644 lib/certifi-2022.12.7.dist-info/top_level.txt create mode 100644 lib/certifi/__init__.py create mode 100644 lib/certifi/__main__.py create mode 100644 lib/certifi/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/certifi/__pycache__/__main__.cpython-39.pyc create mode 100644 lib/certifi/__pycache__/core.cpython-39.pyc create mode 100644 lib/certifi/cacert.pem create mode 100644 lib/certifi/core.py create mode 100644 lib/certifi/py.typed create mode 100644 lib/charset_normalizer-3.1.0.dist-info/INSTALLER create mode 100644 lib/charset_normalizer-3.1.0.dist-info/LICENSE create mode 100644 lib/charset_normalizer-3.1.0.dist-info/METADATA create mode 100644 lib/charset_normalizer-3.1.0.dist-info/RECORD create mode 100644 lib/charset_normalizer-3.1.0.dist-info/WHEEL create mode 100644 lib/charset_normalizer-3.1.0.dist-info/entry_points.txt create mode 100644 lib/charset_normalizer-3.1.0.dist-info/top_level.txt create mode 100644 lib/charset_normalizer/__init__.py create mode 100644 lib/charset_normalizer/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/charset_normalizer/__pycache__/api.cpython-39.pyc create mode 100644 lib/charset_normalizer/__pycache__/cd.cpython-39.pyc create mode 100644 lib/charset_normalizer/__pycache__/constant.cpython-39.pyc create mode 100644 lib/charset_normalizer/__pycache__/legacy.cpython-39.pyc create mode 100644 lib/charset_normalizer/__pycache__/md.cpython-39.pyc create mode 100644 lib/charset_normalizer/__pycache__/models.cpython-39.pyc create mode 100644 lib/charset_normalizer/__pycache__/utils.cpython-39.pyc create mode 100644 lib/charset_normalizer/__pycache__/version.cpython-39.pyc create mode 100644 lib/charset_normalizer/api.py create mode 100644 lib/charset_normalizer/assets/__init__.py create mode 100644 lib/charset_normalizer/assets/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/charset_normalizer/cd.py create mode 100644 lib/charset_normalizer/cli/__init__.py create mode 100644 lib/charset_normalizer/cli/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/charset_normalizer/cli/__pycache__/normalizer.cpython-39.pyc create mode 100644 lib/charset_normalizer/cli/normalizer.py create mode 100644 lib/charset_normalizer/constant.py create mode 100644 lib/charset_normalizer/legacy.py create mode 100644 lib/charset_normalizer/md.cp39-win_amd64.pyd create mode 100644 lib/charset_normalizer/md.py create mode 100644 lib/charset_normalizer/md__mypyc.cp39-win_amd64.pyd create mode 100644 lib/charset_normalizer/models.py create mode 100644 lib/charset_normalizer/py.typed create mode 100644 lib/charset_normalizer/utils.py create mode 100644 lib/charset_normalizer/version.py create mode 100644 lib/colorama-0.4.6.dist-info/INSTALLER create mode 100644 lib/colorama-0.4.6.dist-info/METADATA create mode 100644 lib/colorama-0.4.6.dist-info/RECORD create mode 100644 lib/colorama-0.4.6.dist-info/WHEEL create mode 100644 lib/colorama-0.4.6.dist-info/licenses/LICENSE.txt create mode 100644 lib/colorama/__init__.py create mode 100644 lib/colorama/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/colorama/__pycache__/ansi.cpython-39.pyc create mode 100644 lib/colorama/__pycache__/ansitowin32.cpython-39.pyc create mode 100644 lib/colorama/__pycache__/initialise.cpython-39.pyc create mode 100644 lib/colorama/__pycache__/win32.cpython-39.pyc create mode 100644 lib/colorama/__pycache__/winterm.cpython-39.pyc create mode 100644 lib/colorama/ansi.py create mode 100644 lib/colorama/ansitowin32.py create mode 100644 lib/colorama/initialise.py create mode 100644 lib/colorama/tests/__init__.py create mode 100644 lib/colorama/tests/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/colorama/tests/__pycache__/ansi_test.cpython-39.pyc create mode 100644 lib/colorama/tests/__pycache__/ansitowin32_test.cpython-39.pyc create mode 100644 lib/colorama/tests/__pycache__/initialise_test.cpython-39.pyc create mode 100644 lib/colorama/tests/__pycache__/isatty_test.cpython-39.pyc create mode 100644 lib/colorama/tests/__pycache__/utils.cpython-39.pyc create mode 100644 lib/colorama/tests/__pycache__/winterm_test.cpython-39.pyc create mode 100644 lib/colorama/tests/ansi_test.py create mode 100644 lib/colorama/tests/ansitowin32_test.py create mode 100644 lib/colorama/tests/initialise_test.py create mode 100644 lib/colorama/tests/isatty_test.py create mode 100644 lib/colorama/tests/utils.py create mode 100644 lib/colorama/tests/winterm_test.py create mode 100644 lib/colorama/win32.py create mode 100644 lib/colorama/winterm.py create mode 100644 lib/frozenlist-1.3.3.dist-info/INSTALLER create mode 100644 lib/frozenlist-1.3.3.dist-info/LICENSE create mode 100644 lib/frozenlist-1.3.3.dist-info/METADATA create mode 100644 lib/frozenlist-1.3.3.dist-info/RECORD create mode 100644 lib/frozenlist-1.3.3.dist-info/WHEEL create mode 100644 lib/frozenlist-1.3.3.dist-info/top_level.txt create mode 100644 lib/frozenlist/__init__.py create mode 100644 lib/frozenlist/__init__.pyi create mode 100644 lib/frozenlist/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/frozenlist/_frozenlist.cp39-win_amd64.pyd create mode 100644 lib/frozenlist/_frozenlist.pyx create mode 100644 lib/frozenlist/py.typed create mode 100644 lib/idna-3.4.dist-info/INSTALLER create mode 100644 lib/idna-3.4.dist-info/LICENSE.md create mode 100644 lib/idna-3.4.dist-info/METADATA create mode 100644 lib/idna-3.4.dist-info/RECORD create mode 100644 lib/idna-3.4.dist-info/WHEEL create mode 100644 lib/idna/__init__.py create mode 100644 lib/idna/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/idna/__pycache__/codec.cpython-39.pyc create mode 100644 lib/idna/__pycache__/compat.cpython-39.pyc create mode 100644 lib/idna/__pycache__/core.cpython-39.pyc create mode 100644 lib/idna/__pycache__/idnadata.cpython-39.pyc create mode 100644 lib/idna/__pycache__/intranges.cpython-39.pyc create mode 100644 lib/idna/__pycache__/package_data.cpython-39.pyc create mode 100644 lib/idna/__pycache__/uts46data.cpython-39.pyc create mode 100644 lib/idna/codec.py create mode 100644 lib/idna/compat.py create mode 100644 lib/idna/core.py create mode 100644 lib/idna/idnadata.py create mode 100644 lib/idna/intranges.py create mode 100644 lib/idna/package_data.py create mode 100644 lib/idna/py.typed create mode 100644 lib/idna/uts46data.py create mode 100644 lib/multidict-6.0.4.dist-info/INSTALLER create mode 100644 lib/multidict-6.0.4.dist-info/LICENSE create mode 100644 lib/multidict-6.0.4.dist-info/METADATA create mode 100644 lib/multidict-6.0.4.dist-info/RECORD create mode 100644 lib/multidict-6.0.4.dist-info/WHEEL create mode 100644 lib/multidict-6.0.4.dist-info/top_level.txt create mode 100644 lib/multidict/__init__.py create mode 100644 lib/multidict/__init__.pyi create mode 100644 lib/multidict/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/multidict/__pycache__/_abc.cpython-39.pyc create mode 100644 lib/multidict/__pycache__/_compat.cpython-39.pyc create mode 100644 lib/multidict/__pycache__/_multidict_base.cpython-39.pyc create mode 100644 lib/multidict/__pycache__/_multidict_py.cpython-39.pyc create mode 100644 lib/multidict/_abc.py create mode 100644 lib/multidict/_compat.py create mode 100644 lib/multidict/_multidict.cp39-win_amd64.pyd create mode 100644 lib/multidict/_multidict_base.py create mode 100644 lib/multidict/_multidict_py.py create mode 100644 lib/multidict/py.typed create mode 100644 lib/openai-0.27.2.dist-info/INSTALLER create mode 100644 lib/openai-0.27.2.dist-info/LICENSE create mode 100644 lib/openai-0.27.2.dist-info/METADATA create mode 100644 lib/openai-0.27.2.dist-info/RECORD create mode 100644 lib/openai-0.27.2.dist-info/REQUESTED create mode 100644 lib/openai-0.27.2.dist-info/WHEEL create mode 100644 lib/openai-0.27.2.dist-info/entry_points.txt create mode 100644 lib/openai-0.27.2.dist-info/top_level.txt create mode 100644 lib/openai-0.27.2.dist-info/zip-safe create mode 100644 lib/openai/__init__.py create mode 100644 lib/openai/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/openai/__pycache__/_openai_scripts.cpython-39.pyc create mode 100644 lib/openai/__pycache__/api_requestor.cpython-39.pyc create mode 100644 lib/openai/__pycache__/cli.cpython-39.pyc create mode 100644 lib/openai/__pycache__/datalib.cpython-39.pyc create mode 100644 lib/openai/__pycache__/embeddings_utils.cpython-39.pyc create mode 100644 lib/openai/__pycache__/error.cpython-39.pyc create mode 100644 lib/openai/__pycache__/object_classes.cpython-39.pyc create mode 100644 lib/openai/__pycache__/openai_object.cpython-39.pyc create mode 100644 lib/openai/__pycache__/openai_response.cpython-39.pyc create mode 100644 lib/openai/__pycache__/upload_progress.cpython-39.pyc create mode 100644 lib/openai/__pycache__/util.cpython-39.pyc create mode 100644 lib/openai/__pycache__/validators.cpython-39.pyc create mode 100644 lib/openai/__pycache__/version.cpython-39.pyc create mode 100644 lib/openai/__pycache__/wandb_logger.cpython-39.pyc create mode 100644 lib/openai/_openai_scripts.py create mode 100644 lib/openai/api_requestor.py create mode 100644 lib/openai/api_resources/__init__.py create mode 100644 lib/openai/api_resources/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/audio.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/chat_completion.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/completion.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/customer.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/deployment.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/edit.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/embedding.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/engine.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/error_object.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/file.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/fine_tune.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/image.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/model.cpython-39.pyc create mode 100644 lib/openai/api_resources/__pycache__/moderation.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/__init__.py create mode 100644 lib/openai/api_resources/abstract/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/__pycache__/api_resource.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/__pycache__/createable_api_resource.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/__pycache__/deletable_api_resource.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/__pycache__/engine_api_resource.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/__pycache__/listable_api_resource.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/__pycache__/nested_resource_class_methods.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/__pycache__/updateable_api_resource.cpython-39.pyc create mode 100644 lib/openai/api_resources/abstract/api_resource.py create mode 100644 lib/openai/api_resources/abstract/createable_api_resource.py create mode 100644 lib/openai/api_resources/abstract/deletable_api_resource.py create mode 100644 lib/openai/api_resources/abstract/engine_api_resource.py create mode 100644 lib/openai/api_resources/abstract/listable_api_resource.py create mode 100644 lib/openai/api_resources/abstract/nested_resource_class_methods.py create mode 100644 lib/openai/api_resources/abstract/updateable_api_resource.py create mode 100644 lib/openai/api_resources/audio.py create mode 100644 lib/openai/api_resources/chat_completion.py create mode 100644 lib/openai/api_resources/completion.py create mode 100644 lib/openai/api_resources/customer.py create mode 100644 lib/openai/api_resources/deployment.py create mode 100644 lib/openai/api_resources/edit.py create mode 100644 lib/openai/api_resources/embedding.py create mode 100644 lib/openai/api_resources/engine.py create mode 100644 lib/openai/api_resources/error_object.py create mode 100644 lib/openai/api_resources/experimental/__init__.py create mode 100644 lib/openai/api_resources/experimental/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/openai/api_resources/experimental/__pycache__/completion_config.cpython-39.pyc create mode 100644 lib/openai/api_resources/experimental/completion_config.py create mode 100644 lib/openai/api_resources/file.py create mode 100644 lib/openai/api_resources/fine_tune.py create mode 100644 lib/openai/api_resources/image.py create mode 100644 lib/openai/api_resources/model.py create mode 100644 lib/openai/api_resources/moderation.py create mode 100644 lib/openai/cli.py create mode 100644 lib/openai/datalib.py create mode 100644 lib/openai/embeddings_utils.py create mode 100644 lib/openai/error.py create mode 100644 lib/openai/object_classes.py create mode 100644 lib/openai/openai_object.py create mode 100644 lib/openai/openai_response.py create mode 100644 lib/openai/py.typed create mode 100644 lib/openai/tests/__init__.py create mode 100644 lib/openai/tests/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/openai/tests/__pycache__/test_api_requestor.cpython-39.pyc create mode 100644 lib/openai/tests/__pycache__/test_endpoints.cpython-39.pyc create mode 100644 lib/openai/tests/__pycache__/test_exceptions.cpython-39.pyc create mode 100644 lib/openai/tests/__pycache__/test_file_cli.cpython-39.pyc create mode 100644 lib/openai/tests/__pycache__/test_long_examples_validator.cpython-39.pyc create mode 100644 lib/openai/tests/__pycache__/test_url_composition.cpython-39.pyc create mode 100644 lib/openai/tests/__pycache__/test_util.cpython-39.pyc create mode 100644 lib/openai/tests/asyncio/__init__.py create mode 100644 lib/openai/tests/asyncio/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/openai/tests/asyncio/__pycache__/test_endpoints.cpython-39.pyc create mode 100644 lib/openai/tests/asyncio/test_endpoints.py create mode 100644 lib/openai/tests/test_api_requestor.py create mode 100644 lib/openai/tests/test_endpoints.py create mode 100644 lib/openai/tests/test_exceptions.py create mode 100644 lib/openai/tests/test_file_cli.py create mode 100644 lib/openai/tests/test_long_examples_validator.py create mode 100644 lib/openai/tests/test_url_composition.py create mode 100644 lib/openai/tests/test_util.py create mode 100644 lib/openai/upload_progress.py create mode 100644 lib/openai/util.py create mode 100644 lib/openai/validators.py create mode 100644 lib/openai/version.py create mode 100644 lib/openai/wandb_logger.py create mode 100644 lib/requests-2.28.2.dist-info/INSTALLER create mode 100644 lib/requests-2.28.2.dist-info/LICENSE create mode 100644 lib/requests-2.28.2.dist-info/METADATA create mode 100644 lib/requests-2.28.2.dist-info/RECORD create mode 100644 lib/requests-2.28.2.dist-info/WHEEL create mode 100644 lib/requests-2.28.2.dist-info/top_level.txt create mode 100644 lib/requests/__init__.py create mode 100644 lib/requests/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/requests/__pycache__/__version__.cpython-39.pyc create mode 100644 lib/requests/__pycache__/_internal_utils.cpython-39.pyc create mode 100644 lib/requests/__pycache__/adapters.cpython-39.pyc create mode 100644 lib/requests/__pycache__/api.cpython-39.pyc create mode 100644 lib/requests/__pycache__/auth.cpython-39.pyc create mode 100644 lib/requests/__pycache__/certs.cpython-39.pyc create mode 100644 lib/requests/__pycache__/compat.cpython-39.pyc create mode 100644 lib/requests/__pycache__/cookies.cpython-39.pyc create mode 100644 lib/requests/__pycache__/exceptions.cpython-39.pyc create mode 100644 lib/requests/__pycache__/help.cpython-39.pyc create mode 100644 lib/requests/__pycache__/hooks.cpython-39.pyc create mode 100644 lib/requests/__pycache__/models.cpython-39.pyc create mode 100644 lib/requests/__pycache__/packages.cpython-39.pyc create mode 100644 lib/requests/__pycache__/sessions.cpython-39.pyc create mode 100644 lib/requests/__pycache__/status_codes.cpython-39.pyc create mode 100644 lib/requests/__pycache__/structures.cpython-39.pyc create mode 100644 lib/requests/__pycache__/utils.cpython-39.pyc create mode 100644 lib/requests/__version__.py create mode 100644 lib/requests/_internal_utils.py create mode 100644 lib/requests/adapters.py create mode 100644 lib/requests/api.py create mode 100644 lib/requests/auth.py create mode 100644 lib/requests/certs.py create mode 100644 lib/requests/compat.py create mode 100644 lib/requests/cookies.py create mode 100644 lib/requests/exceptions.py create mode 100644 lib/requests/help.py create mode 100644 lib/requests/hooks.py create mode 100644 lib/requests/models.py create mode 100644 lib/requests/packages.py create mode 100644 lib/requests/sessions.py create mode 100644 lib/requests/status_codes.py create mode 100644 lib/requests/structures.py create mode 100644 lib/requests/utils.py create mode 100644 lib/tqdm-4.65.0.dist-info/INSTALLER create mode 100644 lib/tqdm-4.65.0.dist-info/LICENCE create mode 100644 lib/tqdm-4.65.0.dist-info/METADATA create mode 100644 lib/tqdm-4.65.0.dist-info/RECORD create mode 100644 lib/tqdm-4.65.0.dist-info/WHEEL create mode 100644 lib/tqdm-4.65.0.dist-info/entry_points.txt create mode 100644 lib/tqdm-4.65.0.dist-info/top_level.txt create mode 100644 lib/tqdm/__init__.py create mode 100644 lib/tqdm/__main__.py create mode 100644 lib/tqdm/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/__main__.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/_dist_ver.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/_main.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/_monitor.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/_tqdm.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/_tqdm_gui.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/_tqdm_notebook.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/_tqdm_pandas.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/_utils.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/asyncio.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/auto.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/autonotebook.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/cli.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/dask.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/gui.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/keras.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/notebook.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/rich.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/std.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/tk.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/utils.cpython-39.pyc create mode 100644 lib/tqdm/__pycache__/version.cpython-39.pyc create mode 100644 lib/tqdm/_dist_ver.py create mode 100644 lib/tqdm/_main.py create mode 100644 lib/tqdm/_monitor.py create mode 100644 lib/tqdm/_tqdm.py create mode 100644 lib/tqdm/_tqdm_gui.py create mode 100644 lib/tqdm/_tqdm_notebook.py create mode 100644 lib/tqdm/_tqdm_pandas.py create mode 100644 lib/tqdm/_utils.py create mode 100644 lib/tqdm/asyncio.py create mode 100644 lib/tqdm/auto.py create mode 100644 lib/tqdm/autonotebook.py create mode 100644 lib/tqdm/cli.py create mode 100644 lib/tqdm/completion.sh create mode 100644 lib/tqdm/contrib/__init__.py create mode 100644 lib/tqdm/contrib/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/tqdm/contrib/__pycache__/bells.cpython-39.pyc create mode 100644 lib/tqdm/contrib/__pycache__/concurrent.cpython-39.pyc create mode 100644 lib/tqdm/contrib/__pycache__/discord.cpython-39.pyc create mode 100644 lib/tqdm/contrib/__pycache__/itertools.cpython-39.pyc create mode 100644 lib/tqdm/contrib/__pycache__/logging.cpython-39.pyc create mode 100644 lib/tqdm/contrib/__pycache__/slack.cpython-39.pyc create mode 100644 lib/tqdm/contrib/__pycache__/telegram.cpython-39.pyc create mode 100644 lib/tqdm/contrib/__pycache__/utils_worker.cpython-39.pyc create mode 100644 lib/tqdm/contrib/bells.py create mode 100644 lib/tqdm/contrib/concurrent.py create mode 100644 lib/tqdm/contrib/discord.py create mode 100644 lib/tqdm/contrib/itertools.py create mode 100644 lib/tqdm/contrib/logging.py create mode 100644 lib/tqdm/contrib/slack.py create mode 100644 lib/tqdm/contrib/telegram.py create mode 100644 lib/tqdm/contrib/utils_worker.py create mode 100644 lib/tqdm/dask.py create mode 100644 lib/tqdm/gui.py create mode 100644 lib/tqdm/keras.py create mode 100644 lib/tqdm/notebook.py create mode 100644 lib/tqdm/rich.py create mode 100644 lib/tqdm/std.py create mode 100644 lib/tqdm/tk.py create mode 100644 lib/tqdm/tqdm.1 create mode 100644 lib/tqdm/utils.py create mode 100644 lib/tqdm/version.py create mode 100644 lib/urllib3-1.26.15.dist-info/INSTALLER create mode 100644 lib/urllib3-1.26.15.dist-info/LICENSE.txt create mode 100644 lib/urllib3-1.26.15.dist-info/METADATA create mode 100644 lib/urllib3-1.26.15.dist-info/RECORD create mode 100644 lib/urllib3-1.26.15.dist-info/WHEEL create mode 100644 lib/urllib3-1.26.15.dist-info/top_level.txt create mode 100644 lib/urllib3/__init__.py create mode 100644 lib/urllib3/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/_collections.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/_version.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/connection.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/connectionpool.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/exceptions.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/fields.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/filepost.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/poolmanager.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/request.cpython-39.pyc create mode 100644 lib/urllib3/__pycache__/response.cpython-39.pyc create mode 100644 lib/urllib3/_collections.py create mode 100644 lib/urllib3/_version.py create mode 100644 lib/urllib3/connection.py create mode 100644 lib/urllib3/connectionpool.py create mode 100644 lib/urllib3/contrib/__init__.py create mode 100644 lib/urllib3/contrib/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/urllib3/contrib/__pycache__/_appengine_environ.cpython-39.pyc create mode 100644 lib/urllib3/contrib/__pycache__/appengine.cpython-39.pyc create mode 100644 lib/urllib3/contrib/__pycache__/ntlmpool.cpython-39.pyc create mode 100644 lib/urllib3/contrib/__pycache__/pyopenssl.cpython-39.pyc create mode 100644 lib/urllib3/contrib/__pycache__/securetransport.cpython-39.pyc create mode 100644 lib/urllib3/contrib/__pycache__/socks.cpython-39.pyc create mode 100644 lib/urllib3/contrib/_appengine_environ.py create mode 100644 lib/urllib3/contrib/_securetransport/__init__.py create mode 100644 lib/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-39.pyc create mode 100644 lib/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-39.pyc create mode 100644 lib/urllib3/contrib/_securetransport/bindings.py create mode 100644 lib/urllib3/contrib/_securetransport/low_level.py create mode 100644 lib/urllib3/contrib/appengine.py create mode 100644 lib/urllib3/contrib/ntlmpool.py create mode 100644 lib/urllib3/contrib/pyopenssl.py create mode 100644 lib/urllib3/contrib/securetransport.py create mode 100644 lib/urllib3/contrib/socks.py create mode 100644 lib/urllib3/exceptions.py create mode 100644 lib/urllib3/fields.py create mode 100644 lib/urllib3/filepost.py create mode 100644 lib/urllib3/packages/__init__.py create mode 100644 lib/urllib3/packages/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/urllib3/packages/__pycache__/six.cpython-39.pyc create mode 100644 lib/urllib3/packages/backports/__init__.py create mode 100644 lib/urllib3/packages/backports/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/urllib3/packages/backports/__pycache__/makefile.cpython-39.pyc create mode 100644 lib/urllib3/packages/backports/makefile.py create mode 100644 lib/urllib3/packages/six.py create mode 100644 lib/urllib3/poolmanager.py create mode 100644 lib/urllib3/request.py create mode 100644 lib/urllib3/response.py create mode 100644 lib/urllib3/util/__init__.py create mode 100644 lib/urllib3/util/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/connection.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/proxy.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/queue.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/request.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/response.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/retry.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/ssl_.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/ssl_match_hostname.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/ssltransport.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/timeout.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/url.cpython-39.pyc create mode 100644 lib/urllib3/util/__pycache__/wait.cpython-39.pyc create mode 100644 lib/urllib3/util/connection.py create mode 100644 lib/urllib3/util/proxy.py create mode 100644 lib/urllib3/util/queue.py create mode 100644 lib/urllib3/util/request.py create mode 100644 lib/urllib3/util/response.py create mode 100644 lib/urllib3/util/retry.py create mode 100644 lib/urllib3/util/ssl_.py create mode 100644 lib/urllib3/util/ssl_match_hostname.py create mode 100644 lib/urllib3/util/ssltransport.py create mode 100644 lib/urllib3/util/timeout.py create mode 100644 lib/urllib3/util/url.py create mode 100644 lib/urllib3/util/wait.py create mode 100644 lib/yarl-1.8.2.dist-info/INSTALLER create mode 100644 lib/yarl-1.8.2.dist-info/LICENSE create mode 100644 lib/yarl-1.8.2.dist-info/METADATA create mode 100644 lib/yarl-1.8.2.dist-info/RECORD create mode 100644 lib/yarl-1.8.2.dist-info/WHEEL create mode 100644 lib/yarl-1.8.2.dist-info/top_level.txt create mode 100644 lib/yarl/__init__.py create mode 100644 lib/yarl/__init__.pyi create mode 100644 lib/yarl/__pycache__/__init__.cpython-39.pyc create mode 100644 lib/yarl/__pycache__/_quoting.cpython-39.pyc create mode 100644 lib/yarl/__pycache__/_quoting_py.cpython-39.pyc create mode 100644 lib/yarl/__pycache__/_url.cpython-39.pyc create mode 100644 lib/yarl/_quoting.py create mode 100644 lib/yarl/_quoting_c.cp39-win_amd64.pyd create mode 100644 lib/yarl/_quoting_c.pyi create mode 100644 lib/yarl/_quoting_c.pyx create mode 100644 lib/yarl/_quoting_py.py create mode 100644 lib/yarl/_url.py create mode 100644 lib/yarl/py.typed create mode 100644 requirements.txt diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..315e917 --- /dev/null +++ b/__init__.py @@ -0,0 +1,175 @@ +import sys +import os +import bpy + +# Add the 'libs' folder to the Python path +libs_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "lib") +if libs_path not in sys.path: + sys.path.append(libs_path) + +print (libs_path) +import openai + +openai.api_key = os.getenv("OPENAI_API_KEY") + +bl_info = { + "name": "GPT-4 Blender Assistant", + "blender": (2, 82, 0), + "category": "Object", + "author": "Aarya (@gd3kr)", + "version": (1, 0, 0), + "location": "3D View > UI > GPT-4 Blender Assistant", + "description": "Generate Blender Python code using OpenAI's GPT-4 to perform various tasks.", + "warning": "", + "wiki_url": "", + "tracker_url": "", +} + + +def init_props(): + bpy.types.Scene.gpt4_natural_language_input = bpy.props.StringProperty( + name="Command", + description="Enter the natural language command", + default="", + ) + bpy.types.Scene.gpt4_button_pressed = bpy.props.BoolProperty(default=False) + + + +def clear_props(): + del bpy.types.Scene.gpt4_natural_language_input + del bpy.types.Scene.gpt4_button_pressed + + +def generate_blender_code(prompt): + + try: + response = openai.ChatCompletion.create( + model="gpt-4", + messages=[{ + "role": "system", + "content": "You are a assistant made for the purposes of helping the user with Blender, the 3D software. Reply only with the code, without markdown, preferably import entire modules instead of bits. do not perform destructive operations on the meshes. Do not use cap_ends. Do not do more than what is asked (setting up render settings, adding cameras, etc)" + }, + {"role": "user", "content": "Hey, can you please create Blender code for me that accomplishes the following task: " + prompt + "? \n" + "code:\n"} + ], + stream=True, + max_tokens=1000, + ) + except Exception as e: # Use GPT-3.5 if GPT-4 is not available + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{ + "role": "system", + "content": "You are a assistant made for the purposes of helping the user with Blender, the 3D software. Reply only with the code, without markdown, preferably import entire modules instead of bits. do not perform destructive operations on the meshes. Do not use cap_ends. Do not do more than what is asked (setting up render settings, adding cameras, etc)" + }, + {"role": "user", "content": "Hey, can you please create Blender code for me that accomplishes the following task: " + prompt + "? \n" + "code:\n"} + ], + stream=True, + max_tokens=1000, + ) + return None + + try: + collected_events = [] + completion_text = '' + # iterate through the stream of events + for event in response: + # check if role key is present in event['choices'][0]['delta'] + if 'role' in event['choices'][0]['delta']: + # skip + continue + # check if length of event['choices'][0]['delta']['content'] is 0 + if len(event['choices'][0]['delta']) == 0: + # skip + continue + collected_events.append(event) # save the event response + # extract the text + event_text = event['choices'][0]['delta']['content'] + completion_text += event_text # append the text + # clear print screen + print(completion_text, flush=True, end='\r') + # print the text + return completion_text + except IndexError: + return None + + +class GPT4_PT_Panel(bpy.types.Panel): + bl_label = "GPT-4 Blender Assistant" + bl_idname = "GPT4_PT_Panel" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'GPT-4 Assistant' + + def draw(self, context): + layout = self.layout + column = layout.column(align=True) + + column.label(text="Enter a natural language command:") + + # Add the input field for natural language commands + column.prop(context.scene, "gpt4_natural_language_input", text="") + + # Execute the operator with the input from the user + button_label = "Please wait...(this might take some time)" if context.scene.gpt4_button_pressed else "Execute" + operator = column.operator("gpt4.execute", text=button_label) + operator.natural_language_input = context.scene.gpt4_natural_language_input + + column.separator() + + +class GPT4_OT_Execute(bpy.types.Operator): + bl_idname = "gpt4.execute" + bl_label = "GPT-4 Execute" + bl_options = {'REGISTER', 'UNDO'} + + natural_language_input: bpy.props.StringProperty( + name="Command", + description="Enter the natural language command", + default="", + ) + + def execute(self, context): + context.scene.gpt4_button_pressed = True + bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) + + blender_code = generate_blender_code(self.natural_language_input) + + if blender_code: + # Add this line to print the generated code. + print("Generated code:", blender_code) + try: + exec(blender_code) + except Exception as e: + self.report({'ERROR'}, f"Error executing generated code: {e}") + context.scene.gpt4_button_pressed = False + return {'CANCELLED'} + else: + self.report({'ERROR'}, "Failed to generate Blender Python code") + context.scene.gpt4_button_pressed = False + return {'CANCELLED'} + + context.scene.gpt4_button_pressed = False + return {'FINISHED'} + + +def menu_func(self, context): + self.layout.operator(GPT4_OT_Execute.bl_idname) + + +def register(): + bpy.utils.register_class(GPT4_OT_Execute) + bpy.utils.register_class(GPT4_PT_Panel) + bpy.types.VIEW3D_MT_mesh_add.append(menu_func) + init_props() + + +def unregister(): + bpy.utils.unregister_class(GPT4_OT_Execute) + bpy.utils.unregister_class(GPT4_PT_Panel) + bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) + clear_props() + + +if __name__ == "__main__": + register() diff --git a/lib/aiohttp-3.8.4.dist-info/INSTALLER b/lib/aiohttp-3.8.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/aiohttp-3.8.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/aiohttp-3.8.4.dist-info/LICENSE.txt b/lib/aiohttp-3.8.4.dist-info/LICENSE.txt new file mode 100644 index 0000000..e497a32 --- /dev/null +++ b/lib/aiohttp-3.8.4.dist-info/LICENSE.txt @@ -0,0 +1,13 @@ + Copyright aio-libs contributors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/aiohttp-3.8.4.dist-info/METADATA b/lib/aiohttp-3.8.4.dist-info/METADATA new file mode 100644 index 0000000..4289464 --- /dev/null +++ b/lib/aiohttp-3.8.4.dist-info/METADATA @@ -0,0 +1,253 @@ +Metadata-Version: 2.1 +Name: aiohttp +Version: 3.8.4 +Summary: Async http client/server framework (asyncio) +Home-page: https://github.com/aio-libs/aiohttp +Maintainer: aiohttp team +Maintainer-email: team@aiohttp.org +License: Apache 2 +Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby +Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/aiohttp +Project-URL: Docs: Changelog, https://docs.aiohttp.org/en/stable/changes.html +Project-URL: Docs: RTD, https://docs.aiohttp.org +Project-URL: GitHub: issues, https://github.com/aio-libs/aiohttp/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/aiohttp +Classifier: Development Status :: 5 - Production/Stable +Classifier: Framework :: AsyncIO +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +License-File: LICENSE.txt +Requires-Dist: attrs (>=17.3.0) +Requires-Dist: charset-normalizer (<4.0,>=2.0) +Requires-Dist: multidict (<7.0,>=4.5) +Requires-Dist: async-timeout (<5.0,>=4.0.0a3) +Requires-Dist: yarl (<2.0,>=1.0) +Requires-Dist: frozenlist (>=1.1.1) +Requires-Dist: aiosignal (>=1.1.2) +Requires-Dist: idna-ssl (>=1.0) ; python_version < "3.7" +Requires-Dist: asynctest (==0.13.0) ; python_version < "3.8" +Requires-Dist: typing-extensions (>=3.7.4) ; python_version < "3.8" +Provides-Extra: speedups +Requires-Dist: aiodns ; extra == 'speedups' +Requires-Dist: Brotli ; extra == 'speedups' +Requires-Dist: cchardet ; (python_version < "3.10") and extra == 'speedups' + +================================== +Async http client/server framework +================================== + +.. image:: https://raw.githubusercontent.com/aio-libs/aiohttp/master/docs/aiohttp-plain.svg + :height: 64px + :width: 64px + :alt: aiohttp logo + +| + +.. image:: https://github.com/aio-libs/aiohttp/workflows/CI/badge.svg + :target: https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI + :alt: GitHub Actions status for master branch + +.. image:: https://codecov.io/gh/aio-libs/aiohttp/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/aiohttp + :alt: codecov.io status for master branch + +.. image:: https://badge.fury.io/py/aiohttp.svg + :target: https://pypi.org/project/aiohttp + :alt: Latest PyPI package version + +.. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest + :target: https://docs.aiohttp.org/ + :alt: Latest Read The Docs + +.. image:: https://img.shields.io/discourse/status?server=https%3A%2F%2Faio-libs.discourse.group + :target: https://aio-libs.discourse.group + :alt: Discourse status + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :target: https://gitter.im/aio-libs/Lobby + :alt: Chat on Gitter + + +Key Features +============ + +- Supports both client and server side of HTTP protocol. +- Supports both client and server Web-Sockets out-of-the-box and avoids + Callback Hell. +- Provides Web-server with middlewares and plugable routing. + + +Getting started +=============== + +Client +------ + +To get something from the web: + +.. code-block:: python + + import aiohttp + import asyncio + + async def main(): + + async with aiohttp.ClientSession() as session: + async with session.get('http://python.org') as response: + + print("Status:", response.status) + print("Content-type:", response.headers['content-type']) + + html = await response.text() + print("Body:", html[:15], "...") + + asyncio.run(main()) + +This prints: + +.. code-block:: + + Status: 200 + Content-type: text/html; charset=utf-8 + Body: ... + +Coming from `requests `_ ? Read `why we need so many lines `_. + +Server +------ + +An example using a simple server: + +.. code-block:: python + + # examples/server_simple.py + from aiohttp import web + + async def handle(request): + name = request.match_info.get('name', "Anonymous") + text = "Hello, " + name + return web.Response(text=text) + + async def wshandle(request): + ws = web.WebSocketResponse() + await ws.prepare(request) + + async for msg in ws: + if msg.type == web.WSMsgType.text: + await ws.send_str("Hello, {}".format(msg.data)) + elif msg.type == web.WSMsgType.binary: + await ws.send_bytes(msg.data) + elif msg.type == web.WSMsgType.close: + break + + return ws + + + app = web.Application() + app.add_routes([web.get('/', handle), + web.get('/echo', wshandle), + web.get('/{name}', handle)]) + + if __name__ == '__main__': + web.run_app(app) + + +Documentation +============= + +https://aiohttp.readthedocs.io/ + + +Demos +===== + +https://github.com/aio-libs/aiohttp-demos + + +External links +============== + +* `Third party libraries + `_ +* `Built with aiohttp + `_ +* `Powered by aiohttp + `_ + +Feel free to make a Pull Request for adding your link to these pages! + + +Communication channels +====================== + +*aio-libs discourse group*: https://aio-libs.discourse.group + +*gitter chat* https://gitter.im/aio-libs/Lobby + +We support `Stack Overflow +`_. +Please add *aiohttp* tag to your question there. + +Requirements +============ + +- Python >= 3.6 +- async-timeout_ +- attrs_ +- charset-normalizer_ +- multidict_ +- yarl_ +- frozenlist_ + +Optionally you may install the cChardet_ and aiodns_ libraries (highly +recommended for sake of speed). + +.. _charset-normalizer: https://pypi.org/project/charset-normalizer +.. _aiodns: https://pypi.python.org/pypi/aiodns +.. _attrs: https://github.com/python-attrs/attrs +.. _multidict: https://pypi.python.org/pypi/multidict +.. _frozenlist: https://pypi.org/project/frozenlist/ +.. _yarl: https://pypi.python.org/pypi/yarl +.. _async-timeout: https://pypi.python.org/pypi/async_timeout +.. _cChardet: https://pypi.python.org/pypi/cchardet + +License +======= + +``aiohttp`` is offered under the Apache 2 license. + + +Keepsafe +======== + +The aiohttp community would like to thank Keepsafe +(https://www.getkeepsafe.com) for its support in the early days of +the project. + + +Source code +=========== + +The latest developer version is available in a GitHub repository: +https://github.com/aio-libs/aiohttp + +Benchmarks +========== + +If you are interested in efficiency, the AsyncIO community maintains a +list of benchmarks on the official wiki: +https://github.com/python/asyncio/wiki/Benchmarks diff --git a/lib/aiohttp-3.8.4.dist-info/RECORD b/lib/aiohttp-3.8.4.dist-info/RECORD new file mode 100644 index 0000000..d07ce7a --- /dev/null +++ b/lib/aiohttp-3.8.4.dist-info/RECORD @@ -0,0 +1,117 @@ +aiohttp-3.8.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +aiohttp-3.8.4.dist-info/LICENSE.txt,sha256=wUk-nxDVnR-6n53ygAjhVX4zz5-6yM4SY6ozk5goA94,601 +aiohttp-3.8.4.dist-info/METADATA,sha256=ThZlzmCRsP4HrQ6TkzZfjJH97oYCJ2IFfxsKUrMZt7Q,7608 +aiohttp-3.8.4.dist-info/RECORD,, +aiohttp-3.8.4.dist-info/WHEEL,sha256=J_4V_gB-O6Y7Pn6lk91K27JaIhI-q07YM5J8Ufzqla4,100 +aiohttp-3.8.4.dist-info/top_level.txt,sha256=iv-JIaacmTl-hSho3QmphcKnbRRYx1st47yjz_178Ro,8 +aiohttp/.hash/_cparser.pxd.hash,sha256=1tLMUc3IzMppOVKW99LKG1TD6szTXGSaCfHgiPOL9aA,108 +aiohttp/.hash/_find_header.pxd.hash,sha256=TxG5w4etbVd6sfm5JWbdf5PW6LnuXRQnlMoFBVGKN2E,112 +aiohttp/.hash/_helpers.pyi.hash,sha256=D1pTrCkUaJ3by1XeGH_nE-amt7XdjfRHcm9oRtoGhHQ,108 +aiohttp/.hash/_helpers.pyx.hash,sha256=MA4zlNd5xukP4VDAbnoId0Azv8HxCpwLWie2gSMPLsw,108 +aiohttp/.hash/_http_parser.pyx.hash,sha256=H1bEygSJvt4Oc3GhF_yMpoO4ZYENKF9uqxK8Qtuz_w4,112 +aiohttp/.hash/_http_writer.pyx.hash,sha256=oqW0CdYfKNwSX0PKREN8Qz_Mi4aaioGCIPbEvsaEgaM,112 +aiohttp/.hash/_websocket.pyx.hash,sha256=8AcsJ5Tb8lZ9_QVXor_1Xbtl5igK1iP5rtEZZ0iA2AE,110 +aiohttp/.hash/hdrs.py.hash,sha256=Vfr7WRlz3YbkgRFBfXksCI8mA7PXUMhs37jnv0kiqWQ,103 +aiohttp/__init__.py,sha256=YlwcFRv0Q3O903_FqwC0d5s3FICP7uc6XhaK3EmLOz8,7086 +aiohttp/__pycache__/__init__.cpython-39.pyc,, +aiohttp/__pycache__/abc.cpython-39.pyc,, +aiohttp/__pycache__/base_protocol.cpython-39.pyc,, +aiohttp/__pycache__/client.cpython-39.pyc,, +aiohttp/__pycache__/client_exceptions.cpython-39.pyc,, +aiohttp/__pycache__/client_proto.cpython-39.pyc,, +aiohttp/__pycache__/client_reqrep.cpython-39.pyc,, +aiohttp/__pycache__/client_ws.cpython-39.pyc,, +aiohttp/__pycache__/connector.cpython-39.pyc,, +aiohttp/__pycache__/cookiejar.cpython-39.pyc,, +aiohttp/__pycache__/formdata.cpython-39.pyc,, +aiohttp/__pycache__/hdrs.cpython-39.pyc,, +aiohttp/__pycache__/helpers.cpython-39.pyc,, +aiohttp/__pycache__/http.cpython-39.pyc,, +aiohttp/__pycache__/http_exceptions.cpython-39.pyc,, +aiohttp/__pycache__/http_parser.cpython-39.pyc,, +aiohttp/__pycache__/http_websocket.cpython-39.pyc,, +aiohttp/__pycache__/http_writer.cpython-39.pyc,, +aiohttp/__pycache__/locks.cpython-39.pyc,, +aiohttp/__pycache__/log.cpython-39.pyc,, +aiohttp/__pycache__/multipart.cpython-39.pyc,, +aiohttp/__pycache__/payload.cpython-39.pyc,, +aiohttp/__pycache__/payload_streamer.cpython-39.pyc,, +aiohttp/__pycache__/pytest_plugin.cpython-39.pyc,, +aiohttp/__pycache__/resolver.cpython-39.pyc,, +aiohttp/__pycache__/streams.cpython-39.pyc,, +aiohttp/__pycache__/tcp_helpers.cpython-39.pyc,, +aiohttp/__pycache__/test_utils.cpython-39.pyc,, +aiohttp/__pycache__/tracing.cpython-39.pyc,, +aiohttp/__pycache__/typedefs.cpython-39.pyc,, +aiohttp/__pycache__/web.cpython-39.pyc,, +aiohttp/__pycache__/web_app.cpython-39.pyc,, +aiohttp/__pycache__/web_exceptions.cpython-39.pyc,, +aiohttp/__pycache__/web_fileresponse.cpython-39.pyc,, +aiohttp/__pycache__/web_log.cpython-39.pyc,, +aiohttp/__pycache__/web_middlewares.cpython-39.pyc,, +aiohttp/__pycache__/web_protocol.cpython-39.pyc,, +aiohttp/__pycache__/web_request.cpython-39.pyc,, +aiohttp/__pycache__/web_response.cpython-39.pyc,, +aiohttp/__pycache__/web_routedef.cpython-39.pyc,, +aiohttp/__pycache__/web_runner.cpython-39.pyc,, +aiohttp/__pycache__/web_server.cpython-39.pyc,, +aiohttp/__pycache__/web_urldispatcher.cpython-39.pyc,, +aiohttp/__pycache__/web_ws.cpython-39.pyc,, +aiohttp/__pycache__/worker.cpython-39.pyc,, +aiohttp/_cparser.pxd,sha256=rEtEshtn54wSf_ZCJ0EBjRyVBkIOv_oh17BcaNOd6V8,5188 +aiohttp/_find_header.pxd,sha256=BFUSmxhemBtblqxzjzH3x03FfxaWlTyuAIOz8YZ5_nM,70 +aiohttp/_headers.pxi,sha256=1MhCe6Un_KI1tpO85HnDfzVO94BhcirLanAOys5FIHA,2090 +aiohttp/_helpers.cp39-win_amd64.pyd,sha256=saSU6G6FDzwzX8L8wnLM3ztPffh6I6LCWFXRffajt50,39936 +aiohttp/_helpers.pyi,sha256=2Hd5IC0Zf4YTEJ412suyyhsh1kVyVDv5g4stgyo2Ksc,208 +aiohttp/_helpers.pyx,sha256=tgl7fZh0QMT6cjf4jSJ8iaO6DdQD3GON2-SH4N5_ETg,1084 +aiohttp/_http_parser.cp39-win_amd64.pyd,sha256=jedetBkAYpdEjskn7uupJGVOt1XKGGA3FGhMgfqtrbk,215552 +aiohttp/_http_parser.pyx,sha256=1AHorYdvPR_sEjQSnCWT9kwGZKWFapVRZT910TUx7OM,28168 +aiohttp/_http_writer.cp39-win_amd64.pyd,sha256=zGoLudoYg_87WtYocd1NAA3KvGemARaQmkhDYBHbNpo,35840 +aiohttp/_http_writer.pyx,sha256=8CBLytO2rx1kdpWe9HYSznhLXdeZWyE-3xI7jaGasag,4738 +aiohttp/_websocket.cp39-win_amd64.pyd,sha256=c_0vaDGix66LSY8sncK3DaPBNQkZDa5aDk3226SyAtY,24064 +aiohttp/_websocket.pyx,sha256=o9J7yi9c2-jTBjE3dUkXxhDWKvRWJz5GZfyLsgJQa38,1617 +aiohttp/abc.py,sha256=h_4OTYoks0wu64YIpvNPMFkFaVqh1cJ7HdygN1fAKp8,5712 +aiohttp/base_protocol.py,sha256=u4ITEnXHJ88gNDngHxiU01ZPQhMy_m2eQTJx0cqwvXA,2831 +aiohttp/client.py,sha256=vP-rZMty2GJbMeWqnwITHZQjdqtFTTGEJGGW9Ndv1gg,46342 +aiohttp/client_exceptions.py,sha256=k-ys_A4QQW-IH4-fQIAYgBj6Zjjf2METxILp2cElMuk,9612 +aiohttp/client_proto.py,sha256=3Cx_gH0ZZNNkVCzhnysBe09zbLhnN9jZX3gvqsp_SbE,8421 +aiohttp/client_reqrep.py,sha256=DKI4Wq2DyHgr6k1IVnKbRoOwvHQXxo3emKYJEy8oIa0,38107 +aiohttp/client_ws.py,sha256=70XQ5cWdvbwgA6tko-1GfhbSJ70bAHjm1OE4mWtWDw4,10816 +aiohttp/connector.py,sha256=h5hT-7KZ02NUBQ2OT3wCvyO0h7TZfl7ENFNBgRJUKEM,52630 +aiohttp/cookiejar.py,sha256=dih2tt5EPa_Z-Xp4oq3xmYYKBQKAIdM4JiZEKHPrvGU,14070 +aiohttp/formdata.py,sha256=iySnD63XJwo4l1TT_KZiJGNzzmn0RtvvF2_FnJM32Uo,6278 +aiohttp/hdrs.py,sha256=ygDnAqYCot8NMQyjck2r9CzpTspAOXpHknNl-yxIybY,4838 +aiohttp/helpers.py,sha256=evbtkLDIbZ3oW8V3A5ODXQ8YPHTfk5t19AzZEKWhfnI,27276 +aiohttp/http.py,sha256=YPu2yu8TQAiQfIiu9JpE4ICmE0D4ljADgLXtNFlEJnc,1870 +aiohttp/http_exceptions.py,sha256=rLwhCbFrOpQ_ntr3GnxaxD3oRnTTNM1utmDDBUbdVTU,2691 +aiohttp/http_parser.py,sha256=gokMNEZeTCHfA52a7LwiGxEShLHAOfSxbCq-8QWh9QU,34061 +aiohttp/http_websocket.py,sha256=a7yQM4cQcM-DxhRITp8dT_0jfWxqbU-yTWTgqH8Zfpc,26000 +aiohttp/http_writer.py,sha256=w0Q5-jEx3V40vuf4VAaKxSitEaiKIUyHRI_qUM0ado4,6131 +aiohttp/locks.py,sha256=vp1Z4zx0SvooSffw88dkZ-7qpk2CqRf5vWh2dpKagTA,1177 +aiohttp/log.py,sha256=zYUTvXsMQ9Sz1yNN8kXwd5Qxu49a1FzjZ_wQqriEc8M,333 +aiohttp/multipart.py,sha256=DqB2JIhnmhpmC3x5KuYaDGPfS6a1yMixnyVRsVSOcDI,33274 +aiohttp/payload.py,sha256=5RuKdJJj1WYwIo_vp-4Jdcn5ZoTzrAYna90DG0xNpXQ,14099 +aiohttp/payload_streamer.py,sha256=6bbqsfgysHzfUTJIUlqad1wRklVuLmNHVtScv_mWhGY,2187 +aiohttp/py.typed,sha256=3VVwXUAWVEVX7sDwyYDnW5ZdBC9_Z9AJAFfLCleUW0k,8 +aiohttp/pytest_plugin.py,sha256=26CllzLUw-JGrwfXYIK-XGbDMCDwbIaomV7Sk4WZVG4,12163 +aiohttp/resolver.py,sha256=UWM-6HJkOLwkGveoesBnGv-pJePtpE7js6ujYi7XHoc,5252 +aiohttp/streams.py,sha256=yWdQ0wIGIQCfI9OGdhzImtQKcUBRNfQ9Dw2pzXm-dCM,21418 +aiohttp/tcp_helpers.py,sha256=K-hhGh3jd6qCEnHJo8LvFyfJwBjh99UKI7A0aSRVhj4,998 +aiohttp/test_utils.py,sha256=gs74TWcysSMuhfAvae0Dy-jKkWVKBWKp2nLt_qfi3x0,22140 +aiohttp/tracing.py,sha256=qOwPt4vyGRVlebUUSuOJ5Vc9cc4ZGOOPr5TuweOSoVE,15649 +aiohttp/typedefs.py,sha256=E-iS7tNE3CrcMAjz5uiwr_Zlp4CKpCU99hzAKw08g14,1830 +aiohttp/web.py,sha256=lzB4xbNvgULFAN6uxb1Yg63qAyIFgapIHYGk3z7uZeU,18669 +aiohttp/web_app.py,sha256=QEkoioPMr8vLTFGgzGEdOWkX5xM9b44neR-cYyfjs6c,17727 +aiohttp/web_exceptions.py,sha256=ZhwkxigtA1hmmxkVQTH9cTRgU_cz5wb-0Enm-dlj_v4,10539 +aiohttp/web_fileresponse.py,sha256=tRRZyBNPL579Ac8Q2mSF7qjHIkV42z9VyVEkvQLOJlQ,11072 +aiohttp/web_log.py,sha256=IxdzjN1glBIssfpWBv6cS9TtyyAaJF0hJHCqt7IxEtE,7765 +aiohttp/web_middlewares.py,sha256=qmHNhOdhTtJZjChDiBlF9M-SGxF3vz2koDnxMPrlNCA,4256 +aiohttp/web_protocol.py,sha256=VVlY1FBTEg5JxtDhYO7bvfamylBEpKffdJFSN9_6cC8,23078 +aiohttp/web_request.py,sha256=nOKHoJdzco_41mfGHUclYrdoZKazD8dCiAbd0UOwQzU,29069 +aiohttp/web_response.py,sha256=Ont7UsoFinG6fpLwuVTOLHHZDWZ5VM0uGFCOiUII-pk,28296 +aiohttp/web_routedef.py,sha256=KrQtG7OzlE1ylPZf54yDJjBLl36nGvYl8Z9mA6SWayk,6368 +aiohttp/web_runner.py,sha256=OGDcOLBeCZuOPNeTa7wi8bLll_7kzviMAx7vRgKs4UE,11538 +aiohttp/web_server.py,sha256=peGS1H8OvZEd4LuB_hqTA_6HQhc4stFeYQIh4XCmZ2A,2112 +aiohttp/web_urldispatcher.py,sha256=r6lC1U7puKU45VHcvQW5mJ6ienTBCi01ouz077kiJHQ,40703 +aiohttp/web_ws.py,sha256=MqL1XuwXeh_xf3X9oAwvNGvVA3zvxcykR_O26EKkL0A,17631 +aiohttp/worker.py,sha256=WlC9ItbJ3NPCHUFIJcbEGOQkC9kWcumURyxfcKYtqws,9032 diff --git a/lib/aiohttp-3.8.4.dist-info/WHEEL b/lib/aiohttp-3.8.4.dist-info/WHEEL new file mode 100644 index 0000000..d22c9ab --- /dev/null +++ b/lib/aiohttp-3.8.4.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: false +Tag: cp39-cp39-win_amd64 + diff --git a/lib/aiohttp-3.8.4.dist-info/top_level.txt b/lib/aiohttp-3.8.4.dist-info/top_level.txt new file mode 100644 index 0000000..ee4ba4f --- /dev/null +++ b/lib/aiohttp-3.8.4.dist-info/top_level.txt @@ -0,0 +1 @@ +aiohttp diff --git a/lib/aiohttp/.hash/_cparser.pxd.hash b/lib/aiohttp/.hash/_cparser.pxd.hash new file mode 100644 index 0000000..cd588d0 --- /dev/null +++ b/lib/aiohttp/.hash/_cparser.pxd.hash @@ -0,0 +1 @@ +ac4b44b21b67e78c127ff6422741018d1c9506420ebffa21d7b05c68d39de95f *D:/a/aiohttp/aiohttp/aiohttp/_cparser.pxd diff --git a/lib/aiohttp/.hash/_find_header.pxd.hash b/lib/aiohttp/.hash/_find_header.pxd.hash new file mode 100644 index 0000000..8af9f81 --- /dev/null +++ b/lib/aiohttp/.hash/_find_header.pxd.hash @@ -0,0 +1 @@ +0455129b185e981b5b96ac738f31f7c74dc57f1696953cae0083b3f18679fe73 *D:/a/aiohttp/aiohttp/aiohttp/_find_header.pxd diff --git a/lib/aiohttp/.hash/_helpers.pyi.hash b/lib/aiohttp/.hash/_helpers.pyi.hash new file mode 100644 index 0000000..82a670d --- /dev/null +++ b/lib/aiohttp/.hash/_helpers.pyi.hash @@ -0,0 +1 @@ +d87779202d197f8613109e35dacbb2ca1b21d64572543bf9838b2d832a362ac7 *D:/a/aiohttp/aiohttp/aiohttp/_helpers.pyi diff --git a/lib/aiohttp/.hash/_helpers.pyx.hash b/lib/aiohttp/.hash/_helpers.pyx.hash new file mode 100644 index 0000000..251b846 --- /dev/null +++ b/lib/aiohttp/.hash/_helpers.pyx.hash @@ -0,0 +1 @@ +b6097b7d987440c4fa7237f88d227c89a3ba0dd403dc638ddbe487e0de7f1138 *D:/a/aiohttp/aiohttp/aiohttp/_helpers.pyx diff --git a/lib/aiohttp/.hash/_http_parser.pyx.hash b/lib/aiohttp/.hash/_http_parser.pyx.hash new file mode 100644 index 0000000..d4d56f3 --- /dev/null +++ b/lib/aiohttp/.hash/_http_parser.pyx.hash @@ -0,0 +1 @@ +d401e8ad876f3d1fec1234129c2593f64c0664a5856a9551653f75d13531ece3 *D:/a/aiohttp/aiohttp/aiohttp/_http_parser.pyx diff --git a/lib/aiohttp/.hash/_http_writer.pyx.hash b/lib/aiohttp/.hash/_http_writer.pyx.hash new file mode 100644 index 0000000..11278aa --- /dev/null +++ b/lib/aiohttp/.hash/_http_writer.pyx.hash @@ -0,0 +1 @@ +f0204bcad3b6af1d6476959ef47612ce784b5dd7995b213edf123b8da19ab1a8 *D:/a/aiohttp/aiohttp/aiohttp/_http_writer.pyx diff --git a/lib/aiohttp/.hash/_websocket.pyx.hash b/lib/aiohttp/.hash/_websocket.pyx.hash new file mode 100644 index 0000000..1a3346e --- /dev/null +++ b/lib/aiohttp/.hash/_websocket.pyx.hash @@ -0,0 +1 @@ +a3d27bca2f5cdbe8d3063137754917c610d62af456273e4665fc8bb202506b7f *D:/a/aiohttp/aiohttp/aiohttp/_websocket.pyx diff --git a/lib/aiohttp/.hash/hdrs.py.hash b/lib/aiohttp/.hash/hdrs.py.hash new file mode 100644 index 0000000..7b005e7 --- /dev/null +++ b/lib/aiohttp/.hash/hdrs.py.hash @@ -0,0 +1 @@ +ca00e702a602a2df0d310ca3724dabf42ce94eca40397a47927365fb2c48c9b6 *D:/a/aiohttp/aiohttp/aiohttp/hdrs.py diff --git a/lib/aiohttp/__init__.py b/lib/aiohttp/__init__.py new file mode 100644 index 0000000..34022f0 --- /dev/null +++ b/lib/aiohttp/__init__.py @@ -0,0 +1,216 @@ +__version__ = "3.8.4" + +from typing import Tuple + +from . import hdrs as hdrs +from .client import ( + BaseConnector as BaseConnector, + ClientConnectionError as ClientConnectionError, + ClientConnectorCertificateError as ClientConnectorCertificateError, + ClientConnectorError as ClientConnectorError, + ClientConnectorSSLError as ClientConnectorSSLError, + ClientError as ClientError, + ClientHttpProxyError as ClientHttpProxyError, + ClientOSError as ClientOSError, + ClientPayloadError as ClientPayloadError, + ClientProxyConnectionError as ClientProxyConnectionError, + ClientRequest as ClientRequest, + ClientResponse as ClientResponse, + ClientResponseError as ClientResponseError, + ClientSession as ClientSession, + ClientSSLError as ClientSSLError, + ClientTimeout as ClientTimeout, + ClientWebSocketResponse as ClientWebSocketResponse, + ContentTypeError as ContentTypeError, + Fingerprint as Fingerprint, + InvalidURL as InvalidURL, + NamedPipeConnector as NamedPipeConnector, + RequestInfo as RequestInfo, + ServerConnectionError as ServerConnectionError, + ServerDisconnectedError as ServerDisconnectedError, + ServerFingerprintMismatch as ServerFingerprintMismatch, + ServerTimeoutError as ServerTimeoutError, + TCPConnector as TCPConnector, + TooManyRedirects as TooManyRedirects, + UnixConnector as UnixConnector, + WSServerHandshakeError as WSServerHandshakeError, + request as request, +) +from .cookiejar import CookieJar as CookieJar, DummyCookieJar as DummyCookieJar +from .formdata import FormData as FormData +from .helpers import BasicAuth, ChainMapProxy, ETag +from .http import ( + HttpVersion as HttpVersion, + HttpVersion10 as HttpVersion10, + HttpVersion11 as HttpVersion11, + WebSocketError as WebSocketError, + WSCloseCode as WSCloseCode, + WSMessage as WSMessage, + WSMsgType as WSMsgType, +) +from .multipart import ( + BadContentDispositionHeader as BadContentDispositionHeader, + BadContentDispositionParam as BadContentDispositionParam, + BodyPartReader as BodyPartReader, + MultipartReader as MultipartReader, + MultipartWriter as MultipartWriter, + content_disposition_filename as content_disposition_filename, + parse_content_disposition as parse_content_disposition, +) +from .payload import ( + PAYLOAD_REGISTRY as PAYLOAD_REGISTRY, + AsyncIterablePayload as AsyncIterablePayload, + BufferedReaderPayload as BufferedReaderPayload, + BytesIOPayload as BytesIOPayload, + BytesPayload as BytesPayload, + IOBasePayload as IOBasePayload, + JsonPayload as JsonPayload, + Payload as Payload, + StringIOPayload as StringIOPayload, + StringPayload as StringPayload, + TextIOPayload as TextIOPayload, + get_payload as get_payload, + payload_type as payload_type, +) +from .payload_streamer import streamer as streamer +from .resolver import ( + AsyncResolver as AsyncResolver, + DefaultResolver as DefaultResolver, + ThreadedResolver as ThreadedResolver, +) +from .streams import ( + EMPTY_PAYLOAD as EMPTY_PAYLOAD, + DataQueue as DataQueue, + EofStream as EofStream, + FlowControlDataQueue as FlowControlDataQueue, + StreamReader as StreamReader, +) +from .tracing import ( + TraceConfig as TraceConfig, + TraceConnectionCreateEndParams as TraceConnectionCreateEndParams, + TraceConnectionCreateStartParams as TraceConnectionCreateStartParams, + TraceConnectionQueuedEndParams as TraceConnectionQueuedEndParams, + TraceConnectionQueuedStartParams as TraceConnectionQueuedStartParams, + TraceConnectionReuseconnParams as TraceConnectionReuseconnParams, + TraceDnsCacheHitParams as TraceDnsCacheHitParams, + TraceDnsCacheMissParams as TraceDnsCacheMissParams, + TraceDnsResolveHostEndParams as TraceDnsResolveHostEndParams, + TraceDnsResolveHostStartParams as TraceDnsResolveHostStartParams, + TraceRequestChunkSentParams as TraceRequestChunkSentParams, + TraceRequestEndParams as TraceRequestEndParams, + TraceRequestExceptionParams as TraceRequestExceptionParams, + TraceRequestRedirectParams as TraceRequestRedirectParams, + TraceRequestStartParams as TraceRequestStartParams, + TraceResponseChunkReceivedParams as TraceResponseChunkReceivedParams, +) + +__all__: Tuple[str, ...] = ( + "hdrs", + # client + "BaseConnector", + "ClientConnectionError", + "ClientConnectorCertificateError", + "ClientConnectorError", + "ClientConnectorSSLError", + "ClientError", + "ClientHttpProxyError", + "ClientOSError", + "ClientPayloadError", + "ClientProxyConnectionError", + "ClientResponse", + "ClientRequest", + "ClientResponseError", + "ClientSSLError", + "ClientSession", + "ClientTimeout", + "ClientWebSocketResponse", + "ContentTypeError", + "Fingerprint", + "InvalidURL", + "RequestInfo", + "ServerConnectionError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ServerTimeoutError", + "TCPConnector", + "TooManyRedirects", + "UnixConnector", + "NamedPipeConnector", + "WSServerHandshakeError", + "request", + # cookiejar + "CookieJar", + "DummyCookieJar", + # formdata + "FormData", + # helpers + "BasicAuth", + "ChainMapProxy", + "ETag", + # http + "HttpVersion", + "HttpVersion10", + "HttpVersion11", + "WSMsgType", + "WSCloseCode", + "WSMessage", + "WebSocketError", + # multipart + "BadContentDispositionHeader", + "BadContentDispositionParam", + "BodyPartReader", + "MultipartReader", + "MultipartWriter", + "content_disposition_filename", + "parse_content_disposition", + # payload + "AsyncIterablePayload", + "BufferedReaderPayload", + "BytesIOPayload", + "BytesPayload", + "IOBasePayload", + "JsonPayload", + "PAYLOAD_REGISTRY", + "Payload", + "StringIOPayload", + "StringPayload", + "TextIOPayload", + "get_payload", + "payload_type", + # payload_streamer + "streamer", + # resolver + "AsyncResolver", + "DefaultResolver", + "ThreadedResolver", + # streams + "DataQueue", + "EMPTY_PAYLOAD", + "EofStream", + "FlowControlDataQueue", + "StreamReader", + # tracing + "TraceConfig", + "TraceConnectionCreateEndParams", + "TraceConnectionCreateStartParams", + "TraceConnectionQueuedEndParams", + "TraceConnectionQueuedStartParams", + "TraceConnectionReuseconnParams", + "TraceDnsCacheHitParams", + "TraceDnsCacheMissParams", + "TraceDnsResolveHostEndParams", + "TraceDnsResolveHostStartParams", + "TraceRequestChunkSentParams", + "TraceRequestEndParams", + "TraceRequestExceptionParams", + "TraceRequestRedirectParams", + "TraceRequestStartParams", + "TraceResponseChunkReceivedParams", +) + +try: + from .worker import GunicornUVLoopWebWorker, GunicornWebWorker + + __all__ += ("GunicornWebWorker", "GunicornUVLoopWebWorker") +except ImportError: # pragma: no cover + pass diff --git a/lib/aiohttp/__pycache__/__init__.cpython-39.pyc b/lib/aiohttp/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f5c7d5dca3d99775f0b7d35dafb4522c28f0ecf GIT binary patch literal 3679 zcmb`JTUXmi631l+Fvb@Q7zmd@E=)3>Bm{Dq+{guNE+#lQw!uTii6X4FtwAMeBsml2 zc|X8@jeXepK>fN;^A+}KyIL~0lgXL0XUySu)K%T3?ykS8TT7*e6!9zmdrJR(Mp6Dl zjkCW&(ReNXy@jZv>?yheiXPFUW(1=UjVSshz0bUaeb6VL`^|nFfB}p_385#$g!73}kQwMsO5HaSX;V3t1e8ah!k&oP}bsDXxuaEMK4q7FJTVAz5dwxNw3=!iHOFj$oRtdF#!QwlrM zM^}{JW<_?#OcN0hEwCaAGxY3Z1&q34W&0Fn|1)|#7g6Z%K(V4riauUdaudJxKUjRS z`0x*sQ7*#!YhA}=e?)>-Uq^Sn+&oXMX&%elmc^RB?eg)wX)wzV4Tf#4yRI=;DmS;T85vmw7%<2VE=7b8O3FeC&Mt+$*ch^Tf(~V@@U=hO8OL?5@wl#nsti z)ova!|0H%s%*7X*SUYyY-AQa1R-3twYgj%XDq5d4)6n-SJACx5hD!Z{bM zmd(ej%>B&Vi>sRq%mu@12B~A=mP`eXGw&-I9%_EG!$$*GIQ?M7$y&Y~#ui!5)NH$? zS;rNo8?MlU$5VTj@#VC`M7u5*e`eJUd&ABL#=WYAQVB4C@(}gagNKDuk z5**yHT`Xw6mb=8`LU=}Vwd;3yD&NrztE2_#=Y8unt(}YU1Sw*lxiT54GmYgXey+L9 z(ULiTv{^G1Oz~SNh~q zJX2mh*x6kzsFn52VzpK|;MrC0*lHFFqDY*{u+1RF#I3F&smL@jx#E_|VL|>g&6m-e;^VE8&Tsu(1J>hW@)4ML~ ziqx*#Eio?4lkf7KT`4|`mUAZMljd4$0Ktq2v?^_UQa$cRv#$0y_*3YM4GnjN-f^xiyq&Mv-p zFB~x2bA%hXWqbbVgn|i+aA&+{r!yl*hs7x0=~_osq2Ms|_*v&^%yVPbzciUcn;DLo zK0BsYV?tlBhVW%)XAx|?n4+xPavBxZWX5MvHs!YD!0i)9n%hqpAjAlCUvlFFx>vag zLXwaoqzM_qFkyr+Mi?bz3FCwb!X)7`VVWRrXNo&ZxI&mC%oDB>t`VjP*9kWW3xw|o zHwm{0-xG3#9|*SzcL+;_MZ!J8UBWWqKH&l3A>k3>N5W&mQ^GUCbHWPY1>q&(3E>st zHDQ&oM#vKigmuCOVUw^$*d`PSZwNbt65%ajmry3WBUA`gLXEIT*eBEpKM@WHKzL7R z5IzVj@|db>rm3pp1ex6IT1L}$t-bvn+jc}{uiNgCC=eq($B8Kybw5!IRp4#T;R#h0 zm4waoC(13%wvE-q#T)=0!nC1)U0t^u^=YJF)(Pws5_Oq9~RoQ zEZf(pmU*fQ1HVc^O4&&P@Bag!-lwku)B`dJQ7zGub~+>rSqPu8kXU!>Y{VZ2UpP6yC!={ rw|I}L$v`w2O-BB4EfYybvXO=OLVRUdJoV4Uw#KrZXe1-N>B#>8Q72Ux literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/abc.cpython-39.pyc b/lib/aiohttp/__pycache__/abc.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..756d70ac016f461b76c208d80c2ec84c60895af4 GIT binary patch literal 8706 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 YmYsRtw#L6*xakgF3N}l9+F=dnU!_HP#sB~S literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/client.cpython-39.pyc b/lib/aiohttp/__pycache__/client.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d25f46c1922757c0d8f9a0285e7246de6c95bf67 GIT binary patch literal 29354 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 Ih5z&a2T`5gEC2ui literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/client_exceptions.cpython-39.pyc b/lib/aiohttp/__pycache__/client_exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0df19ba169549ea8b16255026abb597f4c35834f GIT binary patch literal 11256 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T3>?C~>z+w_%9?Em!Jv zA3*mtvUIoP)&ds5#tA-61Qp!e@L57fxhg7%Pv%x=*U5=A@FIGsKCaDhyqJdF9_!xIZX+F}yk>}o91_R@BxIj93Hk=f)Fz-G{}Lf% z9i&!!IDr&%-rR*DwJqod17|ecHSie?U;8T(L1*@rdEu4mzB1oOxS%UlJnepDOk=b? z9Wu-fbB|0E%=a zSva~DA473_KS%P|+hEqi`LlG2!y8*VAJlh$Jaw2Lf_F^7I1w<`5n!O+UU;d`TM5Jj z_aG+WoyP00AxX}>8^w$4-ftPfVew5jkFgS9wJ31_|x`ok=DQsXigi zg4yWg5RN43wu+#21ZNVx)>+CpUS@U0*w#Ui(Q@zLY#x(wu_cU`P}#=`Bwc=<);o=i zfujo!FDEyg|7N<*0dvkaf=_%ogO`USS&Xf?sqUk=um4Y^eGqRu&M;@UBcW%yJxZZN zMo+Lz&22`;JSr|F$T+qg89R)ZsZTb;Ob*SJonR(EVFz=LqGevX=9eLeeK+A2XtCVMaj;TFg``f&Xjs~8!Q;bc9GM6*m-iE z!fm}R>E8uUeuk(lJ5&AiJRJ#tO!MYbhK1|DZ>{gzC$^h?dfge!f0?aCDX6cyp%m1W{Z z)(qZImH&2y$lvE=ABhJPAH^xS%h$Of{>M<^C}5q6=$y@Ws*3J3)d5QOQ$l_jUqxJ~m{0!Ejn7N$aw>BD)HzDXva%Hm3mes$H%LsQHL~xqmX3h( z9Yi$!FdP3J&&z=$vRJ(I302$ij>GH){dcqXv44jD`$*_Wn>7zu zc{7iD=d6`8^X4$NbkfRO&P2f}T*d1Y1}GhM3dalC!XD%Y$DKj}C1{aDFUxekb?`5j Pyp=}YLLREzo^bvTj##ub literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/client_proto.cpython-39.pyc b/lib/aiohttp/__pycache__/client_proto.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca8a3887d325c11a519e2d2637d6107abed5dee6 GIT binary patch literal 6154 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 O1zurGb_Hg*VEz|D7l;-B literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/client_reqrep.cpython-39.pyc b/lib/aiohttp/__pycache__/client_reqrep.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b7247c58a793b9793b5153e4b314d45184f3b65 GIT binary patch literal 28022 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/connector.cpython-39.pyc b/lib/aiohttp/__pycache__/connector.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae32aa43c09f989ea944546cf20a4ed35766fb74 GIT binary patch literal 35696 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/cookiejar.cpython-39.pyc b/lib/aiohttp/__pycache__/cookiejar.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..670b7afe438f97d448344421dcc6a127bb6c7a8f GIT binary patch literal 10712 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 HrG@_mt(5nG literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/formdata.cpython-39.pyc b/lib/aiohttp/__pycache__/formdata.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c044da263263edb003bfc0d9006587500edb753 GIT binary patch literal 4529 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/helpers.cpython-39.pyc b/lib/aiohttp/__pycache__/helpers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46a2ac23aa7d4bfe9879e2142ed5f5cc19d23628 GIT binary patch literal 26239 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 WXK$=Gwr(K&01;ZWkQoTBIm(s* literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/http.cpython-39.pyc b/lib/aiohttp/__pycache__/http.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2f7dbe3806f41787d0ba658bcb6f5d80c5a7895 GIT binary patch literal 1316 zcmaJ=+iu%N5GAR*NJ*A_lWZqedP&P%?VwMEQ8abf*l27Eq7tE71q3Z_WZ7Jj*`@2M zf&Zm_>_dNOUken-FZ89IS<24kp#*q1b9lJiGdttgYE=!c{XaXwVM){e!sPv{0Liy- zi@yP$Yk?N%p+8FDBh0Zp$+H3}=vt5q@?nvcNJ+(ou*@o?qT*s$WhOD1MJ!e$ zHC88e)*ubGKo->dQrKiI(qfBbku8xWwoI1U3Rz*RWR3a)!#m)#_4CY^t(Ymr?=@Bfw`km{q)OI`}7XEOY`&| zEdYP}mC@1g28Fc)?p)2Ty#=q@p>4?0fuAJPXd(-P^CYBib>MAXnx6MF-k(zE~`qF#Z8R+^z}Iv>HKvoqeK!%g3h^S-@llv z4q$D6LS;))nQG42rfd#fZ}-`e>pbxe9M^sP)RC<@wSV~ZnkxeLgY%=T59zTRkI!hT z_9`3Kd0%b$Jz~hGDQw;xy1QX4=x!WPSsA)l+rdyw2D609>V@#=FR3@7k+kM}$FW&gF?ya*+imA>Qljt*VNl?~VFzi|4Vd-VNa__*)z zbrA42LI+_D0XkXZWrPX>wm3HtEQA_D9if5HL}($bAgm%RAuJ#)BP;^^+U?EKlQ??# zX6vV|mzNLVvHkM0>rdko@bmD^_9ef8+6@5vJ~5Gf00$49BJ-HYoR|q&OlR0p+(2y( zKx!O^j5JUK6&%wfh0YSndA5wF>d61!7OATRF6gmPNBgl&<>sANw3OLY4zI1x|9GL! z{o}e~t!2sc{4n%9S$q|9=BLuS8ir{E3>E>brd&2a7)5dFD?j)_Rt~cAoiFI#V9=X4 z_nsdGA(W}YY1Y|7RZ-@qQTm6*@8DJu{@LICdh{F?5hHiZr^)CE6=!Ljj3!Cyi4&Z* zQ3@mVX!LA)JQC2k_Y;4726H1uSLdXTC7H?c4vT~HkbZ+REwF#8dRbpEEZx$(<*#b( NQhqb97yq^L{{iy=aKiuq literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/http_exceptions.cpython-39.pyc b/lib/aiohttp/__pycache__/http_exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33d604060851f78aa3657bff8c996fe56a3e76c1 GIT binary patch literal 4271 zcmb7HTW{OQ73PqXM9Gq@II$f!Nz2{dqITo9K`&b)T?Cs1n*fd)Y@8MljIxYoWU--1 z?aXLX3+|Hx>{EfhuLJBp`l0`);A>x+zu*GherHJCY^P`_%rOr+ocZQ_=jO1{s9Crk z{e6QU;qITL%wH~)yBO*p5ZvlooN+s5`*zpfce)Op}N;6?#>8(5ujEdhWKbxiemysU$y^+%CzTZo^_`u)Cp~5}khY9xwBIH7< zVljHZm49uHY(CgD`xGB-`8U(Tgl#{cQ@qKi73(cbdzkOx>(qZPd?ga?M+sO`Lr?=e zp&v&|`)S`FhLKceJ4wW3R;TS`E}_aQ#Wq=`*rM%94|8Od1O-A?mO_tY(xXr5axf?c zY>ayt>e~>9U}?l;^N<}{fpzFWvO^ov<`(SCuw%Y1Eol~SyP2y*+&{D3i6tADa=LSG zYxkLg4R@dRWHj8pFVqX24tMv4I#2_$?yeR}-`?Ghp6{wii(A96_X74;yJ3`)!~TRs zFz2nqgUkzpD2a3sY!Glds8#V;8Nc!M3w(2PGQG{@66Q{AKp&Dl$QH&1E4Vqd{$zcL zY=FP)OZ&BhbK9M9ZR#1nY1^~Io-kR*H|DUpXhO{@L7=n@g6~67WMPYquU(AH%sYpu zgij2jHQX31gvR3F0@8V10xGw2%I=KqE&r5#gy^jXK`#!K3IeiE67Gv2kS%y+^qj;D*%#afQYBL00pF%DVGrI#9o33`^ zWF<8kBCV0(GB=ca3cDG%n@wW!3f|6K&`karZAbfBti{Ibi)WcdMa{4)*J**PBxWRD zBb81vqb_R{j@A=#48{#>M!Zk+41aXQ4=@zvWzPn8wxnn0cp~H6#ZZL)JWu51S(69= z_QK-w2Bu2NR-Ja~?Z{`aW^VO8eDq5UV-tcU>B-wcQS>Z=0w5>5@+3Tn(~v(%kbEZv zaD3zAPzm3JpeZ-Qd{SPIROiG!iMSgmYCFkKX|gWv%;jcu0R|JS&AAwneh1 z2c}eyZ+B7+9O-Cp;PX(2hD8y0)2*nCiQ2AluGY;C}LI0uvTb1*49=u$s| zn0i`SIP~K-caB|?n9w4Z_ERZ*JqQzj?D@|RP}2RIEczCV5eU zkhe%|k}%n076g>+9%JO?9Vvh98`l|MnLG9@f!XrMv;v8f^4TymDa_QI@6sf*gAucI zMb_WOP?X6%+xo_om;Xq5evY$=1r_0+a0b3yQR>?$C-^tw*fy-8wS86ZmJMqauvW?% zkCM;BIO2KJhcur#11=Zq1y(gp8SP`3*}{#6?KL~H<=P?p3N?eWlRzfE`VL7-Y0Ld}B&S3pse6qD$~Y^O;br* zPA!4?9*+Nis|1BYB=Xwl3CcAJJV(S8dC-slYL3=v@c64tXWwf43Ty6 z=7pT7>*0yuqOVLZLoI7a4NO*jF!@*5^pd7gF^z#Kyprmjq#`3Jg`1pU{7TX>CG9z9 klqtB(yOX!dAJM^0&*wTVyKY0i*Q&M3Ew}j9TGdwdzwhi~FaQ7m literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/http_parser.cpython-39.pyc b/lib/aiohttp/__pycache__/http_parser.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0fcf6dd308c074acd75d3f1cfa31f8d98b1553d2 GIT binary patch literal 18022 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 Du+1Nl literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/http_writer.cpython-39.pyc b/lib/aiohttp/__pycache__/http_writer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ff27754c8397c231dcc3bb352f90a7602dbe32d GIT binary patch literal 5587 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/log.cpython-39.pyc b/lib/aiohttp/__pycache__/log.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa9510fdd516679f892031548fc1c5e6be1bb9a1 GIT binary patch literal 448 zcmYk3zfQw25XPM*ZPGyaGx7)>C=w$=2nmKF6_$!6vQ)Y8DYX)k$Y-d?)4<5U3uR^E z6_~grwc4|M`hC8$KU)@26f%m3?=$)6F!s}m{cuU#(!?1h$)uCBm;w86#8uI3z6?S z5cC`9`oMNq5E%GVrj=yw<%QVIs4W5+)U@`*11 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/multipart.cpython-39.pyc b/lib/aiohttp/__pycache__/multipart.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..448d76e772f78afc5ebf4ac6732702f111695068 GIT binary patch literal 26294 zcmbt-dvqMvdEdPDfyH78f*=Tj6t$#Cil9h{qNsSTTxdCn0jS|;?q~|0)ZciUkPV2Nylhed$&zvM}>ZFNkpERx0 zG)K0j{(j$`nSB6Mx!S?{RB1}`gSETshf0U)_mu9b zA1)oP-&?x3ex!7yeqZUn`u(N*rA@x}K>fkegUAn+-sREO*r1e8VX{ z=HBUTitpi9o^rRmV!Pw+)*GqPG4wm(ZbQG@u3M$YQQGd_hSF^)9ry0{j(dkMop5)& zWZ};3ZV`8i-o4lD>t^XBYIeH2P_qkdPhlkY%6sgsM(I7ccZa(h_ja>&=`_lF+`TC8 zMcXs3dES`Z_c}3WdeRJ2u6Je0LuP13Rp!09%KYV-RvQ<>>{E+Dwb`iD z!t8mrZ_ImPYG!G%h9~*1F!lZh?uX8N#SbR!aA2{be6M`PuZ5YV#+9XJ;DrNAs)h%f zu7?JL$IrZPsTNe_wZmP7v#NP*In2w==6w$$GvyO!&Yyki{F#|E zPfdrzaplSB<4>L7EDLeXlP9O&GjlrJ8kc9z9h*LX>g2g{(lnpk0-PqD&^dnk*f~t> zsrR0o4hwn?^=jPz2SH&}` ziuDW&k2l?Aj4E!h?eU6xyx9o6MsT9)FE;%u7g3KsoLKiJp03oxt#}KTZ(cuJb~_J; z+t;ZqFI2Izl{!aVI9{vbedj#9ui<<1MAGg*uH|F6{5%rRAXx)RBSKqh3RTmu+F`a_ zW^d(km@Aj-O?Rore7;=1vQ(+*JFgn*0koFnjp^e@XWx&d@Mq7@tLozH3D3VAG#6(t zECyx&BG+m*@ciKL?32~GSpdO1xQIPcx#0P;m1^^15G>wZCo&SYQ;W+oj$<4nsIZYW zrTimJv;PluDE}eC#^7jQI<(xZ+vg77gt-ubP zOQ|4z-MVSIL$9zO{BsMSc%-N4l~mjCKkgb;P|EX0+gQ!G8*iG=nJej)%qNX@`nu`= zQjlF82y$)Xx_Q$CeU92zjg@RW^RlJB+Rm&Fx+6Eul>v8Cd*B6A{n7eQv7W)PLXlePB$v>*mX*e@gCeXb*mT}V*-8fp5=dWoj%6fw)A1CblZyh_-7bh z#vOCFVC42neq{*b1czGux;ws-Zw&YM(i#qi+i2l#y>71zK4UcYI!3VZWoy|o4gTwG z|EzK4m!CBn)*++uiaW7Vh(>OyL+&<|x<m>wX?*S z$=jxh>b}rHbrk5_gXhF~;`n*u`6!YAICkA^js#|4F?Tt2-gwoV1`BcKn$4QJ9c@C} z_kveV^$_xXdo9dX{c_N}>@}WbkKE9P*}ChN^lj?lT64Zq^BMU$&YPu6-F3wXkrQt~0GTr`O-Y-y7yGJ-m0LrhlLVc%d*#_bJ?sM|$9$jZ{Cl z?ocN0D2@;Cd<;K&m?T?a4kJ_x z^Y^{$!3WNv91ju1^cKvb2bj^5>yA-Yo1$?%HnM~1(AuC%h!w1OSjFR~t*sC0~Q!1L(&WO~Wjh4*u;jTlc>+Q1o{dYOJ`3CFU># zfJ*%sy6@e&#BgFz3YV-U@hvb`0h6`~3S)i2cI~!t6Oh7&XgRa9`##vRJ~Y{~X72$t zu#|@_yfg<9fQY4E&^OQQQp5A-D~nzkEn?JrC;K>l?hW%H*@Jc;uolDJXBed(4Ma4OWX+0jbgN2m8SgS&SzFJWY z2zY+zFej1&S8y>*)tXl!qNTWsi($GZzaj}J?$B@q0yJgg6CP8M9w&#@yKqY#XL6d! zdzp|tC}MdjOTkRbrdZv;57V*(@N&pF_0YcTEr+QWDmBRW*hT1{Y;Kq? z%T2wf-otK1JUN6MQPgiCG3;Ud8)cf=YddDvDwtWIRn~IMw3)MXe%Q>TCMWew)5zz{ z5nP>o9#2}9We%gRVCKWb) zf)trIjT0c5V4yab50thO#)qaMia5AZE9rJ>)pj9niFlHMaFbniTn9wplkKc)xM|t= zs0~tW2aL;7s~`gDcA7%bm8Wri<;v4-%l{fj=Vn$jZHQbeX*b(WV_g3oOw*>ICL+YZ zN(S#6Sp3g!Ze<`qi}rv!c%6hNjc4)vWPZA3@A3DqAzltZaW7%A8%f*v&~}X71UWKS z0SAl~xwf@nJq>Pk1*V1bhI$wcL3eY0K%64w#m={;4yh_`s7pv*HA`6$!~La&F8(m^ z@CC2oU0YO-ejSZuk2+@S?zdAIIvgT-wnH8D4n51aCfihzwy9^4Ob%eLHdO#TP7&Sx zaKLLU0W~U6g`Q(8l2cVe5~iWXf<>qgvWDAjGAGES7Fn@?q?BD~M4QJ(FP*un8t#Jd zVaKS;Y%O%jXHGWDJ;;3(mwyC_VP}QjX0b)^4_hQ}rr*liqd@u`^7sezCPmKS{s^wT zso29jAMXC+vOR{&50G@!Bv^fboJ2av1+?x(kWOfC_r1&}?YRnkbL}#DC0_NF2xI4XwS`Vizr+FMwA!c!aQ?8SdN}H{nUGwh&)>Pac0@b?W zE}5qckbSk|LCI_J$cItcKZJpDxk81%T>d@WjhW%Dw;4gy91NC_QwNdsPznNx*K4?Z zGBgTeH^Bv5MS<7r4J}0`gUz|}(o<^xktyU!3 z&^%FeD?z0Q6G3s#1Gd55;!Z*EB07Ybh;V^=1ZA(epgxA$zPYGRqwY^|=|!+wx8HIR zbSFK7?}7RO^uocD>j3Viz@24?jcOm(!;b5rkcqvdb;rqiHSn=Pt_8(o+EK+mCkP7g+CNEa!%KvU+zQmwWeQ4&&*+7&N7m z7n)U9(+WNUZBy^%pWrOlTSDIW3r>~GVWe8!l&p6rM{t`QQb*8I-N)p9Bw?mpcAN9% zvibN6WK+&RPdYy)7yZ$QPgdA$6yv8PRd){aXh&(DQ|TrP#%fi zw9Va)(x!<0+~)2;X%wZ~@s>evGYs^b-6DFWp*&q0>$x-NZSgjH<22Y~lx#(ldAhW< zr`3eF4XrkJwW1+CUE1yq$z1Pr_u-wlc`1x&5`FAzn%Pp&>}vB9Uf-ESQ~CdEr%IxMD*3CfE=3P2#<2U&hm2N`dZNtm7WUBjvG)3741VyyTxa6bRVBk7SIjI|z zx_p&2Ln4^k)2Br5nk=Z7@xE}NoQyRaTbQwdGk$`OE2!tY; zt?@N)i}-oZo7k9_P~3OO!_{>>pgzu|Lyd$Gk#vo648oqNl94^HGm>?fa?eO6&{4lM z0iy}zoJ5jGDOr*QbhS7APp!tG@5KYkCk7M|tF$ei7Ea<`T9^?a4seH6l&UPl zz9DwCFef`tZ?ByEl>KVU697Y25&@OK4jsO!ACJC(J4G%Z8KRM)_QM@Cf#2NPW#p@1 z5OU-NvUtR$_>Y8(mn_~#k?AZRa;Dmag@HaBFW8FJ7?wrss%fKxOCW1)>$>>>woBRJ z67y;3=TFl}1@(X^(MFnH&4|o}#b1Ha)rMWyZ$DwUsq2)3x%BDQGyO{+(VwC~s@Pl* znZ#f8=Bo=;&jo33d0m*n;?bxr^U3O|Rm^XC=6js~P_A_^Ap@z-t-8fVGtdGLC0VLo zvcg~+zyZiY^-2vqr0%(sSy@x{chN<-MH5iHe#EAjvaY=Wrb2D%XZV;68mWGYxhzLe zt2R6cl(ObuVQoq}EIIQv6`2A$vdovz;1HLd#E41~mwv<=wnnHH4!FSMQ2Y&%86R->r%!GN2V5o3(_#b)9%LCSz>%uQcT!3Xz&Ah$Yr)A;OR_`Y(N z@@^)8u=WzXVKS-$ymp9kp?Qro`57R9gMZg;(`by`17Dy&b_nJy{(BY|&>?-87&jFGeDT!v{;H;4S)$Y6OR#*dt-wlk%!y<(DUa9bp_9~1=)AN&CJ(iZX=*9Jm! z^Q5_9E(EefcCd zPo(i@T1Dbbx1a&hfi#&S1b?SA+Yn?D)^YIQ%R;kiNuFh@m|APB zkH*JF1XIk(u)$nrSL+I@3BDdc6Hd?tcDzI`WZCUVI` zYbIIoeVE66QYMhBShb}26~=9dhV<=e!F5Lc1H5orZ0(S#Gp@H#!Da>&$nZjmTAHUz z5V{&AzgHtEP-gNEQPB^XbEx|qA;Vp0SR?lETG5ZMPnI}d-VB<{jy;XcIuNjYc-yH- zEW$$8fkYr-EJ2D|e*h^CkdYmTUJ^T)#1G{w$o+r*fHN6%*>R)1D*jxmR`c)HuW66K^!>(q}5Ey_>zViND?-U@D3 zxbd>*EtcnMmBwZ5jPzwcK;G&=bObX}-(W&yi{-5Z*-As85VdlwLG?iuu62y%%LMBf zo{qr^J>JU0w8qQ&;TjP0nFPdqj0nl=I4;3rf*8OtTMzeGn5$aePZiD5Rh(BnRyZr88Hcn zmJ;DwWCVy13Pg#KeVM4Efmvijt(Ie6FLf)cT^fs2Q*?o<;Woi~D)aMACBh7Kk&dPl z%hs{Bny5B_LwpYbs||m_Q~fHfUlh-D$BYYv*p4}96ViBDE7XatCy2UH6KE4|=(`ON ziSV)>Wg96uq+Yg#h8|vR%h`hA;~q!hM{wz zRb70Yx-}x5Tv)@a!SWju5KkM;s|_Juw2;Sp<7g3r9IbHbb4&;=#gT_jt5%&0bDU)z z(x4A#>idp;p#03^XJ*ca`E#%uAPQE35@K2}z?41M#|8f_`g@HK6@?%AUKTV=J3Ae# zwWAM-`<7}AfJxc}z!WWS3EO44yAP?sOq;Kr$2Xc&rUC{pV6*!tZ z@D>avyw$gmz}9mA5sg={-{J-Se6_4%ss!qH_)H(1e;swdNjQ%p zV+fq-ROSVp+RVPyy7TQ}U5*Eg{qe8(f;A+Bz)u!nHwxVgfM^MuRun2tmmGxpkfdmH z*rPE?jD!T*uo1LDO{d3>#mMo8h66TUV{g@+g5->G`14GtI;&q~@)Jx-Oej}tS(A)N{TdT8GF4#m zZ1uSK+ooW+J;cyLUsu8P4NhVK{A>qi|h{j|+B;6a)lM&7-THKMtCIVhxBrmY(f7 zo!MYbbsswro-3-q=`uUjQBw!D7l^}qNstzta13LUa@cJ1XbxHY4h_lhC zk~A)Dfx?x@JY9z}Kq5uJ;cV5PGI^Z|w=~o@M2pNrTle9f?kp{l@UMv0!^jxs1VWV} z*$26PE*$|h@p2*ZBYjnloUDZ=AxVh7hRc5f33&!3A!ui@_R5$Na18}$K@f$kM2#c1 z&cfEQrtOt+ft)>Er4&ACs;{79EN=rpid!U1>G2TJV|MUAr5;B2n!Bz^_Rn$Ou`tRU zG6qz2DqLe`Yg;cjh$k;EfcSI-jXSP?!EjOX^kT8oa43TKyjQEmP#4gDm(R-9`V-{- zGA1ioYdo)5vu1}7nvdb~aW1aMr|&s9@Mo3MUZ#}w7}S-cuIs`5>e>hm1J=PSorjM* z4c~YMK7Bdz0bl(F57CfbCh~{#jvW4ghknG(V~-7Che@ay{MVbjQF&GeiEUozSsgGo zhPSdOePnJ+=M7SeV-Oh2xI5lezZGxcu{o!-)yqjM9-DJY@YYLdhkH9p+oOJq?oO0$ zi%PrPJ5btz19*JrZg&q#w@3H(x_6>fbjf}9Nvul&+vO*> zQH=kX=XD&HPr*n>j?p*H0G%xomIL!1L?EQ_jw8i_AWe%CDRY4}2(Z9F5b?Yq;KN4BFfKulH~eG{q$0W$O(^JFq3L5UxkKxpdIaTGB>*Am0|8pzZ`* z5WE1<2fMgri5CL|4Qp<;UWASdOX|`*BD6HlA%+n#wKzJdrQp*uGiPx}zpYEbJ}Q!J za_4CAsF3fY&wn^NtTr_-N9?A0Pue}7be|~N&|F627nHB+*iG;Pzk~Q_VYA}@Vm6_T zkg-1TCma%2Vcf~V1fTSbmcry-0q;i3qq+ z;$2egGNc?RF7%mE)Hl)Hy14jh+<1tiCT1B{7d41*ahj)VCq?h_-s0{Vn(kr+%( zpPI~w!6+Or*DK3&9vp@e%GX6O{t^QYC$uM#dV3A0GwH60AsH6*y`;Qh!J~snwt`oY95!2T$@?LBb>Mw>jPDu;YKgnDlC8ebO zW1O&v2yk3bU9=GX(?7{*8P?2`Bty_>92v||$J9o#op4ZSYrWOl74Lv#`I8BC7?uUj zK}u4NLcGFZVI*8LsmEF5$kHXOp>Hm4VFb@|E~MgygJ{m~SXI4u`w-zXXoxPQw{?vJ z6!e4|&LV?_k~69BletrLNlaq~jz}1n zV7uXco*1Hmf6{6FEEQ$CGR1{K|4kIa3)dH+_IzSfgjM1BwW@Rs;Y!h8s)}JJf^0%J z*V14YhvK82{G;%_MQ@DYpU_!tuo$SvA-K~J6F&!tsE)b95v#;#(j;0ip~k}VH*_td z)Y2Iji?Xr;f5;yH8J;AJ+_v$0(3Ci@KEYX$dA!|B^ zj5crt5G}Eq>I6}32g4(pBOF)wIYs`Ad=a0#q-$O9TTo7-brFLy4U__agYD{ zDGa`~H)*;D!7-!^&0?(y>&dwUA2$39Z#j;f0I}Ax%pP&DXFV{C4yi1nfIWfG zVy2Wrxpyv!saXfM#OE0tkrPz~4|KVBKgMG3XrUXDpwPQ|gJULFroadT_*zLRZrL+J zJk{~TegK7p$i)W-7+AF%XVvL0kJsHK$0R2zPSzzCF(jx#j0w0SvYRL>Hb1XxHGJpOaD=0WH=A7{C!MGX@U> zNblz@3E8neZ+$@mdj#Atiwh8S$jinv4LD>FdzS2|*5H9;W74Z2kp2e2DUcTC$_c7# zOuop3>kv~k0v?AF7pt}Ag(Xk@bKdV;hc{98Gs!wYJLf*q@0>=DrKLa0uibU(|>=M;up?;?DEMIw5B_@eK>1LZv}fxf(a_ zhYR(wuMsXI=oq^dkrNs-8D7_SNJEqSZlR$EBFd%s8SSg_~jHCdy=@P+E zX3Q9BF9~8vym%~@J$SO=FM&1dCwj8n7@@$3rD<%%W6WMo|8)RLH z$$lo(vb)TTERHQ;~B`|7imtO%Ha@-K4dcJ!AKREsPrKdn5e|C!>^4vI7qTlO8j;ckBw|v z9d$R|G)tQ$H;UXCj)6GRhDT3$>|#8rk;5mb+3N21Ch+!c95u$V-Q5Z!84XY=9R1ji z@^%=c^zoA&?p^D&y&d0fO2N!@0OH6_9P5CITAR?kJL0?;;QHUegDa`hZj319ZHVT{ zV=5fy9`_*Lv=LC;i_!2PN~*M1%-VN?s7$E?fPR<4s`rn!b54b+di9#;@(g4KC+8_A ziFl-~79?}=A6X7rr2!MmbF~g%5|i{*5E@4+j#`egghUDyF_$M6boEiRUPFrUCM|k%&Hj2r8!h{Q z1J8km90D#3p-Xa0UK+LHK_TzjyZ|oDFbAg2IDb^lY0Tl9<3HwmhQ!sB1rG}F7_1uSdq0L& z>%3f_ll|l5 z2#S1Bf;apFbOIw{6oD)V+vw8k&xz592fU}^{V#{kRpdag;DC&Lh68E^bc!x7G0$VQ z_9qYN7e?fZr_;4~Or^2{T~+zt;}Xuwo}8VP!Y*Cdr5?u}_yR;uK94+TIqe$Z2)AR^ zEhAr1P$({dl>HpOCL5rSu-%Uo$FVU}yYWUbiXiaWp+Gt09PSIj~s!$(Gl$KZ?k zLzt>ox1x`7*wYB`{|FUZUIOc{AOo0U;d=w(@P#A<#ld29Ab@qK6W)ee2F*X{ zrl@+!bEzbdj*4v>&peN3GO!OyxD99rK9E8+7thcRm63C*DabZ2LuGrpZTm0dQz?+N z*k3RJm%MQa=SsMqZF@m<7dc2j(3(8v(v}R8akWcJfgw4OLW);40~b>tQ10Jhdg|XZ z`43EfGtM#S;4BN@V)DC8{u7fh&Q0buriy)Vg0*u@ew)d5CV#+$(9^mG&%$a3#|4W$ z{jva;Q4tOXUMKWb9_wYm3SU<>c5|Jl(45Cvp%A>8b@JHZR2bqi?b_PN!8+OwaXzV| zUiV-?jDhp$Hu+;djq5lrzlemp2KL@)r(tLC3vS#?)=SuBpyzgL+i9-`!IdU^jax;D zvZnksHLWH|Cv4dnG7EJFdW}h2g5Z649S%mjgi+EbFeq)*a){rpZT+mjt8LF@NH#LK zWHL`91JMJFEwC0qEO^!o$fu`Q3~V7JyiY=MKjOc@=x&O^l*9v`X`93Y0LTCxf$|cs z5A>sG+}gVa1pTqo8X7YUqjB+Rlq;>B7Bz`v~PRJZhvF?WyTV9&scV zK7Ql8bxWx8*&fb8;*p>bhvr818GLu?EIyggXu;fp|K^P z8CxDg0}P35v&CSXB3uno9S^jvm{)RGneNs&!NYnX2&vZSd7Q>iT3|(Mb3a*=7lR0Fz^m|u=;Xf{1jQWT817?oKNGQem6)Tb zrmb4|*telJpcoE4j-cq1*XBL>{IkM%;MC&X0p3lO&w^5`+gTy_Ut~@UT|1c*;TE6K zsl&|csXeT^6G@m|R85fmU|GaYty~lN)D|X0dTlu+>ry;HtPN{I!RX`CWb9tvki931 z_P=?fvyr~bqS(j|Aos5++EVUj6yx@dg_L}B&a}Rt&mxu)|6o3(;Bz}Jd;5mroyJ>o zHssBG-uR(q?Et*-5thw}YTP7dnR#^8^T**ohRb)6#MUwo_7sFMf-JcXtXKR9OHT-+ zoAol6@Ksb=$8cu=Rv-prrAj!d=H_8X(-tw<$WY#Zesmz#kSK&B>OlZtTKySb*b_F7 zsfyF+IEO=#f?k9&oG3_P!a>SIun1jB)OpwiGKdeM77Uw02Y(avhNn5MEzd12;J^lu zs|%y?5)FC*IBg=IA?i}_raOuMx^#%1t<*7z{0lU`^+afM1<9scV_Z*&-)rDx5NiP_ zqkgYANVK=ep6*CsAc8Ps8R{mwP$!U!2QLJ;gEBJsqqzS$4nBw021F!!!w@6h`A|#m z@cTBs2!?o}$Yxn^SRUYAUQ2JuKX}O^Q8J6>HsazVgk)L`oRPj{4#MGpU&yNv8z4r+ z%f`>jl1i)JKmt2R>MD*T`06Al=cwP}o&SggX6T4OF|_BnzANh{Q)lyVfJZ<0m2IzU z_XsH7h1;JeC^%5Vo-kXJ387gR2=U864b?~SNDp%-S|%K376oz`u|y*DgYv$1eyJ%z zPTbXJltAa?W-82hiG)G=;!>p7KEXrsfdRQf!V;Grxs*fUI-YVQQhaYa3r^@Pl5%=*um+A|^O)Xi; zvri-|C#(5;(rWH-1&3y0HcMcKHV9Q{<8hpYZ@T#KVPjq&SubUA?nXZD(7(*=@T*+r z-N%FO*15>k#@O@Ed#`ruNEP6l6OfG~|b>|=_Q zK}SEQnIk^y^9Ym=^ihOC=JHHB{F}0c=II-e3-i!iK!m2C>A(ZZ8OTREMKLMquo_0< zXiad8x3};y=c228CC6Drkrq{%3{T>2_$4eVm%es9U4ivY!brnHbYd?0o`QS^ zSbytRB&|d2_#P(WY&?iuX&_L`I#N#}!S#0akGvmnb9)M;x2z0iGoneIVjlG$7wXg%1;m*R{g^w6N4(rcm N{61K)hd*MR{(t0#aNqy{ literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/payload.cpython-39.pyc b/lib/aiohttp/__pycache__/payload.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cea1e6ee20dc5a47fdd448ac8b8c77db3c8973fa GIT binary patch literal 13523 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 dVIzt_C=f`LA}Fc?BCRS}ksQY}n|0&0&CF~= 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/streams.cpython-39.pyc b/lib/aiohttp/__pycache__/streams.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89432985bc062f950eb0a1e97f77d38768c591b3 GIT binary patch literal 18606 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/test_utils.cpython-39.pyc b/lib/aiohttp/__pycache__/test_utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb5ec842388b4e09292363714ccf2a86025ad9a3 GIT binary patch literal 21871 zcmd^nd6XQ-d0%%=&$+X+2NsKi067FN3<)kk@DK%2Bo>PW0TEar*aa!JAdd#qy}PrR zV_DrxV8^qOB2W=+hnAEmha>1^_sMxSWLt?7#gXkehm{v!apIT6Ir=$?^ZZ`klViuS zU!J2QBJ=xw)pP9ZfL5eGvp`o@S65ek_0?D3RbNeMcsOI=@7&Mqb6YPP#_#c=_ir2z zCvo|gVus-wj^UcFRW%(`?v`W8J?6yZZacQz<4#=e2`3@}Ll z9&#Q+`H-8fK3sdmd8BsOIV{h^fU)2dB%iB3T6@fSO!6buBeln!$0a{novIymj@FJj z$K-je`b6!xbG-JX^Q1hFSD&gq?L1w3#(Adp9_Kx^XPswj?{(f=JK>zDz0Y}H?K$T; zskf{8{@U}-^QPgAe%e$mKsQl+p?1?4?#_-M!ai&KWPc^5UZDoyGkeuB5x~cFf)H-t$J>`2b2^>XaY2 zZMz5Ed*85~^WIDDeYZ_F@7|B63ojb(A@_kd4EF(V=vK^`!}EjgLwJ73o5S-(JU{F{ zg6Bu%`4XNFy9GQKZduNs!_%YgV|aSZJB8Ypy)pgIJ%auocc;+9yf?mNc;?E&qP5R( zu6WkU%XoUlJ-TRlSG*5;yZCOO;T{9zPq@e5h~F}uS6Kt&y25wvleaDRDfelN$#Gv~ ziT6R*a-Tu{_qflZ^h4hMm8&wQ_qr$0d|-fr*YJ_F@H;Jy!EKj*$5^@{E}@0fQS zlrVJ5a*w(ep5z}|d)|G)J$ZWwI6EsNI1b37vEZ&-dkMK`-RavS?kRWXjimDtevMH` zJ@+)=nRU;gpOS0M8-))v_g@Jr)k;wD{Jg(hQl6W?ww@0>Kk%o%#&tMXu)^5%shKcW zy5_9EnU~JZy@=21>tSl9RIQe- zRlP8EF7Q+-XsFOWU-5%5b#XPQH0q^lX!H9pex;7b&@Pw!0Il1#M)^AKiTTPBDglh4 znR6G`szK#+r5uE#Jr9>uvZth{vCE|!5o8z8IiM}1!lCKa)oP_& zB9g*19xkud>ljAnRLS?Ed@}k9Sn|~Cc$wAr=yi&a%vS<0+;v4&FO*hSEA^!drJ%fg zuD;j+%p+}7oh{Yfs>gw4=I75xh~uEn`HgBR@Z4#CyUQk?ZsG#TzQVW)P9JSRHk~@Za0=I8noH=pziVsv?oiD4(>ebVpe?4fdUR_$n zSIZ^WQ&&mQ$FH8RT)XN68;`GoW`Sh?YN^s##ylM5`{Ei_fh0kP?BgFEpyS@GWn!2vKwC)l3_w4A{?5(IP+3*e&O=$^o4Nv^6ZuQ+2Zu+ z)0b!G=fjNe1+3Os4KSY<&lg#Db^+9Ju_!fJF|@B$>TWa$^%Po{F@&ZsXw}Ja79T_M zA}(J~}yP!Bt>U~Ux zSsts@gK!8WL5$W*HPF6#j&DhPT`Yw|JruKmSAGJCVU3#^Q=LMt%*Wn8V)G;}{|pf3 z8Bl2qsxwZ^gRpgDxJ)nZ+P4#4!b^H7iY71ZWmbl8Pa>Daeb~z(pY=voMwenpN2Jym zzK!2bxd}JjqPWRGM`niHOxQ_lm(g#;E( zdD*LZpRy7wP(TAh{^~!2-O;w8}A6z8x!<=4X#YITFhFUMA)M->yvrL5h$YMFe zM=pynas5U~E%|yOkoft>+2!3T_wo2}%^#73%x$wkTAQj#JEl=hSRI5JHB%I;ut&4AYZ6kEpqQ8%@b zhBltwNVnMY?qCmVea>(*xTkR+!aeahTn-q$FTzDDx=8YcsK zk4<_Hhftwd)+&>5#xPFV=tG+0){LDvdh}c7iNZeh0eq#-Aqiuvphdfg55sJ+R=VyL z+jI#5w6-cjB}`XVORlS+*Mu1?v?#AdXA(M#Xh4~lOVu@RR;h;4+&C3+<7dO&D^cwZKhFbo!NQ_v*w6P#^=3aAD7KUYxn^~~^3EcPM%AqtX7t2#-!eagA zJ$;L&xAi-gUW~G4+AM{gamx5CYlbt)`MqP#$+E>)GSmU+StkbW zy@PKFJ>tdRL=90f4NL@(k15DnJ2+Omwt7FRmwPeJJW(J+Cd+CMdEU?x}VXJg~P7Opi?MxJ;nAqM!2Zr#3W}<_L@U4C=WcL0Ci|WXWZ1WBe3BBilD@&PC2_ z_TwX}xDCSgEPRz;pF~0k)pJNqpDE-7h;V;IYC?FB2j*F7jO2rnaMe=51<4g+rH`P| z8H&q1Y}EW>LRrAN@@)!kX1;=PGD2I3fxs^$u+Y zQW>o`^3_Hire}W5rwQEi`NA^Zr6_ z*H%!O04Ea-&*Bk>?&k!!8u+POsk0N}#`_fs`+yJ=7LUJ73eLc2B2s3(&89 z%$Ew7kTAkzx$2ebkPl?f+VZ$7J;w#=FQcMf!&y>{X0y$SL4-Y!YGf5@Gt&iw`Zy>V zT+^CsPF~^c)3(jus07RTO5G2&-RainK54T7pL~KZ;i@|0Uc+IT*;D zGR0ykLfgMZMPG&sIoaH|199zo<$jb9)dCYGJTx?;t7UOk1wnx41_AjflRk`K=1LkR z{{e3`N)iKRE~->8=L!#pKqF1RVll)JY7KX-%6zt1d~K~%jozS+_z{Xq6cAd?iZrJh zNWz3(V~P?aObW?W8eQMg?CZeGd0HkXS;4;6XjD}aS*;EYPiqzGjJQ-31%jGmBKYPs zsr#8w%4^~I8ggN3RW(*&)2#O_!3S7{+KWcf4>C8z%EO$4*_&lg{BnwTS07_?i^(UL zbWtg}<1Cv&@*!OQRU|3Xj$3B*FJ}Fz9m_!GT9CRd8-2%$;r_p}dyRK2C`i`Z+54d^ z#YWS3hc|Mia=UWz48FiO-_E9UiFRIkDx;Haf5Z|0d^#H1R+7Lte(qx=Gl)DK`yUa`=;QKR7uEiAL0|$aj zFD~_$Q5X8oU%?HU?;0jl<{r9|2wRp9>YPlh2zwA_ZLz4QP9}_W^WSjk)t81Qx~n_J zj_IbBQDqxL;KCzO{06_};-k-Q#auLDl9`0Ks}SVzR^U}D%yr2|03eb6A+9#p+|%2A zj~P)IQGb!2f0D_YOk`o*$6RM&sUPMeSCy=so~6-Q5aDoyhO$?!woxH4Q6W|W(`S(T zU0nVol9Z<5Z`qb*fl5h<-yF><*4aUSqRH^Xmyvwe^k>np(@TNw5WF}Ucv`yNnVeAJ z2&ga%2Vdz%7fYHDHC@0JfQ|>v|90*Dd!|73S+ozQZmJmRW$Lf8LsC(j0`Cfe;8!r+ zp}arAcU{Vx+%~Qb@hzagxa(b8Tk0``M8@K4{*eyjxP%tz3~tjRRY9n&=50&63E*Rm zwPIN5D6PPupy{*_!@h>L2tS996%%+uA7GyLf|g)hqbQX{Ce_ZOz=`?l^+6Q=)LE2zYYdJ@Fja9p4xv^GPCz;v zZ6`}-r95MoQ=Eh&C`ovuaQ(5?u$z<8F_gxoL>!QCNsddsT~WO;cUoSLb8E>Dv_qOfsbN2&^{ZZLHQg)Af0A&ZH);;b)I7aVv??cJK zo)WzWd2de%_af=WWZU{H?nc^9zNduyk+ySxPYHJMy zM*^5eetxl`^3kSC{u2vdr#-??MfXunya;W6uC5`gop8wrpJBSQ zKdC6hz_5M2lc~=0F;j$bE1Q*mI8?mh!KcD)O1}`-9BvSbyG*6SzlXsOgr68GipI=4 zHvC@+D0w+^Qj|8WO=yzoF!aH070RTq;_~++p?R?aB-51d8W^I|{{_5;0wiNzLjJA> zhN#1b2qEdgFq@)UM6mSw*S0A(sNXfTD9<%0U3Eq}ejG0b88za`MDRw`r_~(c)JJ`C z=jnUsOEwr3JM5bQzCbX@z`nQ$ym>TG?nk@hON)w z63PhZ;e#t13l1)OI`D7BZZX^d`?^$CL2<*lX{C+YT$sf6S}6$B-C^Q)PxQc)>wyW% z_YQziwcmGEFq>bWhQEboJ3S=Px|z~b747=>xGt{LtCj2UxxfdcV;=nWzGAoUBavqa zY)pa%@=Avube%MALtHeawV+W0k1WI0vcZ^8*&^z|e;^h`7m)2dK8%4G&UeOP zHG5edUTs^7Tms)c$OrpDzI1W^-C#}&%&eq-1|9VwoPjTX@JbJ)y^@dbz7J6I{h(gF zv~cd?-2A&>I4jUb^#%f*L_iF@>U_BJ3D>)U0~K2W~G4u+skl9 z7ki-X#W3G}AE54_-%D5CjTSWhqRv1-W2>aatZ$BYi znqHVW`z|QWXrg7UfuP1AGRwsRQo{AB zUf^BtfvH#L(r@n$6t(@fco1(~FPmC9hvTyyfbSXqHpP^b&+DtWd`2MC6Pun;4PvFUfb#$AElxxN4%o0fTrcDg%O1?KYSbHRexaj{yz5qG zLQH8<2leiW(9IVxlcMiJy%5pf1~oR*~^Dy+jAT7 z;4{&@XK;@~X1jZt>LyCnI+FpazWPDD_ze4v%;Je&Vr_3^g7mqU7{;L3g;sFw``eNH zdBXAwO!{!}F_e9_152{cU7&a71`b?S%{ac^zUjv;NWH2}y&EKB>xjr9{K+v0tjGc-fVw;?BZ7b6b zP(Q-tFEJta>DdMR6d$?QsN(@oGAE9NMdoDJgjTdThty9nM`UXLw~x7>W+G}URcZAl zCVz*?-(~W%O#U8|FEjZEOvvsv7p1za$QS!|71gI%My#ukG2ukqeJ}Ag-o}V8GDqws zChY|F3%|{M7`m8{|7Lb~F4`%a=)Gq~V-tyq%(%VnUury_+kKbaMD|t2we2s4r<1t+ zO(eZ;Z^q=pfeN=b9H{Z=iMxk3ju^phhW<-QD0-48TR6*Y>;+B0mA%`Y#M>O4-6QTEJnM+sQ8x+xzCRq9t~6+T$a;o9wKR$q zJ}s_QcqXv(CnCIBZQKyo>l$={faVr-fjrFF_W1$?PQ%r>)Uo_@&4!bfQP_z!rsNkBYwx9M96x@(24wdV@-$i z1P$rLyXlo-+;=?bZ5NcxVubu??ij@qiL-VGdbop~uM`DeC8=wVC*vr@oA5&~dOoNB`Xn_+!V@rnKrj0%ncWOt`NAzNWdNj-ov%(=0ItGc` zK~ha04(UPs&v!U&vUy+M!kFrtq=6D1)3>I2@WvpcegF;^sA541F02c#5=Q4J^Mb_;b z$l<~Zb3jz~@){gCwYx_wseg=d{c;CG@#dbpU}$a!YRJ6>HQFcQPX?lKk(wPzZpZ9_*gC{wTP~t1Azed2d z?6x7h`$3mFJK#$TV{}g<@_&c_`wHN&P_^?&-i@wAPR7NN~K3Z^=0n)`()l zjm=~`x~Q#Wr1a26IkYHBPX;GDIALxYUhK*>tXLg7_D|7sxO+PqaHa@+cU}E6zT;7y zE34C7yMt(45&qxg;2GgyKms!QZNh5q??*uwjXUrV!PFPd zywU*aDPzEHoTqf!QTDPiQ$d4>@B1gX>F|^chpO4G?Ae75VcyO#E=Xy>;% zSh6I8106WjF3NTcwL_}fW$I4=Y!E93jY#C}gla1x)LFv*%SgguLE(Z|Ql}d?>Vsw@ zOd&W>-u})udfeMT8SQF2;Oj8m9D3}XiZ*%SSY`X;yA#E~#K*r&2-qBUxeoUCzilLU z2ZHJw5G3nj=e$~qR<_pX2I7_KJX5}R6w@@EZy}1{OhRn`+pX|dR-O9`s@hxkJy7K{<14Z zHualKNKcB>sgXz|DS?i?_t-Q7+i{lt5|S?xtVv{`sONI&Try|pQj*4Uu~A#|+e!#_omNfX0ic>Sf4uEs&e1 zPIr3z$jP=2P3|w8|0CS<=*D{2Jr7_}K`liJFB?~vF)qmI40fT**Q*eURmN1|;27h) zama?baQw8lsyuz>_Z9sLK81gcPt|WR5&UhZkZ^1VK!3!Ep&DZ(Y}!nw^_@gq{WhM@ zpwBGz=l0n)%npuQ58evlqAcU`??poYqMYooUWR`P=R73J>MaYRU+YNCX=+0}Zyg1J zYt>f=(@UUxR}o+!1cK*jh^($D>=bzI_z!&!Sz8?PDSVWo%10sN`Nm?tq`%}a^!#9x zlcBr}9T|aaSXkp>gnOtU=FUE-UWsGXtBY%siK zehR-((XuyltvChYD~4;^V7xqKTOY+K+b{K=vTf0Q7;k=dM$SUW3`7b&H=p%c@a-mA zmmzEGX8|@3H@k8AAu<)@*a3?$fBkc!cNH7p=d>7bkO78~+kmz!QTeo=y3_3>)?Vk+ zLKufV`sDFB#EJfO;9AFt{tM>lBf&Aw?b>I|U-uCajpOXxwo|`?I?lwxGDe8?I*BgBsNc6IwwGlTkq*eX9S6PlrjChmbK+jIB<|sV4do zamESG5}a{DE`#*2DfL10qUms<3|{W!!a}#FkkP6MozMPZ?Z@u>IVLJX1`bC|#CUcT z|KI}U0$2YV<&2WVvD=6dX+-y{RFvODwtobPHhxKkG*EjG^CJoY=P2DlOMG|r!c?WLd z1Qbq~FE%c7&=jBs)}C!O3vH~RLLK{&D%ehR&@`eiVS&m7+A?=Cp|qCOVJ6=~;tcul zV;1$1I7|m&lcQIO0881Rgb}3PWKA;a!jr z9Ezg*n#bJmyD0S~y>1f{5v$}#PS608QvXLPt(MlSjgqTj8qu0O&f?xI`juuB7CNGS z5>+K~B}{VHABU#Z+bm)~>K!IRS76A7f^0XWZ3X4b&`5`j*QUnyB{_sIIg!*AXK?ue z>P3{2YYpQ_r2#cf%)^ag>?;nhSp2O5a4`hQ-m1 z$T4vDR%T5IV#)T%@)9gO1X7jXD~2tA}*DS7^_2;vkD$ft_qtt3lvbbd4{ zPX@c(G`MWCIMK=N?&KyrxjpzkiSMIU(AM5oCTeRcYAX}$YmK!styC-70>)Zdcj&F; z#(1#56^m*vRPgfxcoHn$f|`e4ce$sP5Mdf?jTaBJ#g9$D-Wgg`$3pXZX!akv z2-D9kd3EpRs(QY~l}WAy-{8X%H>i==j;!l4j?O1ooy%)=AUZVg!y2aI3Mm{EKBB&j zmcrpk;w`T6RB)Kp>aRYwsScus8e>A0A&kT3z%N|IDH(kAC6@0&;*2d^o}QaOGkdu> zJ2!LjG=CH;j!hIGmL`?nej_*6=bFe(G*cMu9Rt0@&aiNgVzl_7_%ggcOzTpwv53<$ zs#K{f;Q)u&5b^FLaUhs7+Zh5&X>+H-t}Z`OHaWxh#!C>1Uh=OyDOI{r{x$BqOyLtx}#pGS(a|+be_+jA61ND0z3l5`4A4BgO4Kf zNKNfO(sM_cK8Ob>eE3I&?~}$S5g90IAB1RN;_wkp47^2ZjyLzUmvpJe)lbTtLf>6` zUixeI#XI|EpMMLNPa*audRsFoWBC&co7S7K5B>dbqL(*2Cvm&}*qD%0n1sU#D;vLE zRPjqUN|k^|&>`159pACkKF&U|peITMUz!lYKd&0U$eE!e6^RUtVtGB#JA+ zuzW$FrBxrpq(JYkstD)0--Dfg4=~5h)fjR=$8L#lBfd8&B?S{p6kK7ha83KPeuQ=Z zE0f=60>jo&SCI>?hA#(Z)qiIBzhP0Vbgit2Qbn9%t>XBEFFVp<0(lB(GAj*fk~zwF zm1ZJp1f`@tl{JiP>fQQ)-Aqp;Jvbh}z5A!;x0N1qJ^Q_v)Q=REFRr?#fUvsIF2k5U2J5Ht;eN z;Z)={P7D%7E4F#QxSz=(CJ!(nS<7#$XvUF6W$3XDt*Jc1mlv6k8dZVGqew7#x&r9P z32mN&R{w>s-oxYtCJiP(%H&BVCz%{$a*D|elg}{uF($W}{1OwoswBk087`i`c=1y4 z%=FB{#mldTxk%xSTufoE?%n9wU{Y`KLwab$<`vUT{Rtn1%aX5)&L-<$D65A-iBU)* z3{%f(vmC#K2}=A`B(|`Zh&^d0h24p)d1NSyXl~^2e$-+K|1q5Ba3%}(le26|v(}_- zS+2LGr^rJ>%bZm4y_fg~U=;04Brmy}3Lf~j57Nk*%HSoACxBz{&s zvy_OeuH{B==4rXHb-1+&enEz%h_FKB~f0DSlj?4Ni9*c>X(5kVT zW@sAm@oK!5FcLN0&}&H}SxXtITA$I!x)Ci_SZ5-hGog>KrL%znNL**YeU8m z_&$-Y4%bGE5iMpWxrXS5B2YtssO+YsxCZU_$j&Ezmfsl4eOhY@(+PRQ+ zM$AGx%i4n>?Ex_d?Hp?lg|r97A!rY=_HamhSR8@&2y2gov^g;k?L2F9A?;Cd4BBI? zoeyb`ixbeEVC~V6_M|uk?J3qC3u#{wd1&*jJs#4Y7H6P6!`c%e?W^J}v}aj+GNfG) z=b$~u+EXFzd2s>S3#@%5qWVCTZ`}9DZK6U-}tyxu}iB}(@B4C!>%;yrK+Rfd%9sd zeMG-sk_8l$TCO~V2He2no%c4YcI9TJY~wa@f9c)dY3gZ1i`9x*x0mp&M%^-@9aNHK z)3Q)91nIl7VK>T+sxwr)Tau;Py<)jhw`J)brB>{dlB|^Kc9}5vKVBa^4yhS4G!Zl6 zX2R6Xq?r=hEPCQ|T_lA5OgH+3hI&Xk!$oEIVyRkPEtMY^^15>*AS)KjY7{1wAM?)lq}?7TiSnG-as6 zd5T3RQ!LgRVzWwowpjdVvs6_YIY`D*S#p>N0h9-bcqkM%OR`ZAx-1%R(}}o7-_-9} zo*#7v?omxIaz#7Se6wVJvr6 zVW+-b`jo3(ES1;I+ZDIAR5cy0=c9TO^*xg)WlM zl155gGy6zt>LLXxCQnGyNYnpZ6B#jpA)w#PV9KNWK{519$7DAE$uLVs#3)O$kc_cp zTuiWJ5Ryrj&_qbNh9H??$*efQl3_^ZSVB`8r5SkF52ly8?aV2;njwoyfo{ir2eXy09J4N*$qs=# zqB#?Ov21l*RWywzYJkHKO(R^(o>|HgR?URMK)s@d6MH^$2cO@0TN z@pGMiT6I98U>%GEE3Se>YIUGD)tu8p5=^+GDf4M{<9>+uI`z+RL*{>`eU2p%xVXn9 zJWltxq{pQ^uFvDr9@p=28IK$AxU9zwdfbr54SU>(;@aJLRGx>uPHIh>X48}xa3fzM zLe3#yCvu6%Wg=IIyg}qmB3FrggUB@^2qI(hTSUGMf_h>P>~ubP3#tY#D+dzOGCjY| zF~3hK6PxGN^7ezk67B;|x)fSDefVKH*i>}So|XbbP34Yl8drvV3*wN&_=YJS?tY9! z%3)N2bLRqGnyMM%IHa-E$spMr=I0{bKYOUk*S++_BLM)>ui7Lf@p6T z*yo}!9AK*=Cy&4J0XZ205}BpB&>Ib}yKlM6>%BqX9uoy&$XBr#_y5*2JKj|5(biDY znQT{nbIY9|)iQN&26OL@GI+voiTi}V^UT(pUR;3Sa3Lyj^WrY*xpV!!H`{qoin4v& zXO6p0_y?cqM?#LmZH{?c1p~eP9WB)@nA)5DJf23`PySx^p3I1=G(V?L}8hU#DXsHk3J;F0}T>Jf(*NDl~91Xw^re~Eec7XRs8G4pZl=%Xcd2! z-L{HC^EF$Ar|2jwfmUf5hD!d04@r+!X=m6ijzG}Aw>IHbMih?ePVe&8;{WP{ajd&@ zdFgwjz)OVaR}*_f!ONs56x15Q;D8qxzw*Hdwnk)z=A~YJy%WdR^eJBFMWL9AKmm{X zwGYG5KvM)UAbIoLS0GsIc+Io77U1P+lvf4}7do79@XM5YP zpY~=jFQual9_=ugV#NRQS*+eeVQ6p7&25V*W%F{cDsC;GLvOG4W;iYXBNx%sW8FB= zAIScXXTIJH_SmTOd99G2lXLW#gG3I2IDN&UXq1aZ^)P#1ru^hmtKCy!Y#R^OUodo1dOf7}?UM-6)o9 zTUJ)_x_(VInr7WeTGfVam0?%MCxN@`xUAD4?t97m*#4t8lV(!Huf@z1eiPTQ#fRS{ zep3k2`{WU1XrAYVsA=QnXO7~%Buz}NIVygkTSyuae4YNoy35KdxKR=*hW zZMA%0+44iDujMF(oxVVe%Rj*bodi&y0~x3y8-$E>a9Q*gD-+k_ zX)Ud>=`T|gcr%+6I%*<`nn=l4QFimvE^4A3l)2R*KR{l&0^%fDTi8iQ;g%05U5N-` zb&{)3u_+P>nXLIOu1?4#-ABl0QC#zCPsou0Ss1rmMNtJwwnf6BC;cz3PB(|2ANFKt5bZY1x0pq$m4cxr#k&s6r z?%7Afp;yOHUD(khn=Hyr=~?4scpOYR&d=Vn!N3!AE`8(fyvSJ4x1E z*b>$t3INbdqtGZ+US zIO&A*PDzo%VTTl!YH1vUNQg9K{UXB$ARxoGe?kmEhBJeZ4Ny8yMyCgLBdhY7gGf2@ zJjrJVbsT%(63t;q$04OtgE|g6D5;y44@nrKNHc?j7JNX$7;D|liG#>7-nu&^4&!d3 zb;rjfjLFvByf_No6wWp##4#M_pc8w#G40*c89g0`8eBaemoR3%dpd`w<18cFhBBxQ z==7a#%<;&z;3WBdOY=u}>IlYgkS!<74fJTUUVfTeZ&YzSCC6tU7BZO}J~(m3Cm24cGmrm;Nxp) z_pDo9R~wTx6e{aP=ovoG<-q+0WPeIdOD-8pr4Y0?hqy+)y=``Wl-BMIw9;3*!$BzI z`u~p0Iu1fxj*qm@0O%3mJksqX0L8Zw+lkK;cW_efwznaflC)RKZz|~2TwTbI=$>9C zaLkGY8E>)%OlVIhgvA-7oZgEUo}g9I%VE4t z`yQnn+Y zsCTL^l*nxPWY9q=}k>s|cxyT}%7!yIHg$w)V4LjqMW z($OsN+aGwa`;@0<|Ik#&{#@8z03L?=kPKpH-EXMxL_nQ~3^qjdNH!z@b*dAp8yNp} z1YmU{B>>pXP~LBBmm;uHz$rf=5|2_hQI;+|V!c8x4qUg{;4FbmgcoWvK?lFp5#T}{}KS8j#7ji$xDv|c#r5QJOI;T&ixQi zor?Q*4otXjZ)w|Fm)^aE48h(_!SYV)ydOU^(8*P^1Or@N3jNZLYUoAlY>Pq_nIUwZ z_v=I9ZKOa4d4=Hpm`DJ_3^z>BBy{k93m{Nu>Z1`L`99juJ759(DWZ8>?N`8^k>zLWN43h#>NW$j3x@BjYJ?>qMGF z{({IBkslEGOCmoc@*^TYA+k;68Ihk7`70uAgv@cAR)ykyLS=x#0x_-rUK9U)c7V58 zGd?=d(rNK8vcp~Y!J(x49ZB*Rv0wa4NzcijagsLvAZ9UmCj}mVGLi|hO(#O@6-lFk z>;rLh+Hy7e@uwuU)yXI4js6<_mrUTlWRlj~hW@l9t6YD^VC!F5Xa;F+aMd{}CF3aC zNYmqmxn{{Bdde`7QF;_Mzxhv2yyfd?7DtD?4=Fj?$Wh99BD|2}#SpJ0csl0U>?CPv z9F;r*aX03E!7fJlqZ+_TUsL~)`W6}?W>Ey3)ed4;HA~-kHmTm4eKFrT-t+`2Z>a literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/typedefs.cpython-39.pyc b/lib/aiohttp/__pycache__/typedefs.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d0602dcdc88f25104869b3089f4615370578e25 GIT binary patch literal 1503 zcmZWoOH&(15Z<>|Zvg_E#Nar#F>+!YlgA;IN(D=@ArX(_ad2BM)uL@+VP|(UvlxqC zkiU{c{ziVpTyx@*ACN+YGJ398kSLEqYMv*AL)F#b@=&5vxx5A=)Q zO~VL{(2NZja}!LhTVN5lVw*YOFc)0rfyW9^U_SV)2t`(c5-UU5R5&}Xuqsqp4Qi|o zb=H6en}7-JbK*%h1yiPh)^Rg*Ph9>BAw0@F4byA}X4oybWg1CSTW|l{ipKFQd_?qP zxPuP=6RS8kYrs6#G{1{=%|F40=JzN^A^w!zhx_aSJfQl1Sd1UC1z2E@;1N}6U{#@k zkU%K7hh<#E7Ct_(Deq@kK=;tZxdV%|&qVI~sy-cysa5*gN>|bIbtWqalSi3}irE}GF zmaju|$7nS!)Py%$zO3%P+UgG4tKIfbYa1`6y^@S%apf$C@?bwk>9>P89!qOEaxE3s zgDi`ZL+S3GW)$P@B~hBRTv=|ft)Iqu)QN_0dgUOQ_a#IehXM9y2LwD-1q zE$hOj<}Q39EnB)Tq9ll=Pl0(lOk-Kx9c37*8ZAqfo(BTkX?hZ&^wv`@FutH`YLKh2 zjX$5F$YpIO=NPbU6j_=GTsp5`gI-hJH|OOs@-=P#4ZlrrK1<}hk&30QAV2CwC#VBk z-$>_Jq=~drA#E`d(#uEcD7d9Ss%`El;ws`PqFxVITj50_^c2(qh15J(ua1`$sVGua zq(%gsQNUwfCs9`3A{TVHgD5#j`J@V&QlzQkCdRb~_%6ppMG5JKrz{iD>~vqO?DcjB zKkjU947wZb%}#e)x^WtW|8;h{W2a3A0hPgxp~1vWP0tBRgAap)`n5bBV&y2$v!$UP zhTzkx&l%OHeTMtjrz+k3QI0|u6$>~IMzX|^rF`@@!ZRp5Cvzl@WLfpzO@?WRe5`a( zD^TgBslZhX$RnV%9h_asK23U)(;b4Qju~GJad}?_Oy9I#-Dh!`@G@8yR)Zvrsi=Q7 z`(F+ISnWoUzt-E|_V)x;?(YnFl=V9(PVzMCA7=SL9I2lAw7q<}-;4J9BFgbe77R~< zLlpfWO4SiPrMY2<2VyB3N&h>RhNm%puWqf_AmW>9Etc literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web.cpython-39.pyc b/lib/aiohttp/__pycache__/web.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98ef788b88963f8cf9c8162c2c657604e7ef3872 GIT binary patch literal 10092 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_app.cpython-39.pyc b/lib/aiohttp/__pycache__/web_app.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a2fbdd4f8175a0695c525535f688407f0d341a6 GIT binary patch literal 15237 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

Vp@sK@m0P~uu>_77C*Xa zxRZq=1a}kA>{j0-_zi;JB={D=9R#-!R0wV-*iJxwtu_)+x2QV_$gS#+3I3Ge&j8@u zME+>aB}=6EyUHpr65Izs9p?WL098`%hPgFu02u#aW^fY>-!e)?Y5jZY;lYAw43+vb z{h4xM!0b26nE}(#2l7J$hK{@ufiNWc&HnO0v7{USUFshw4riFZ&XdEa@0%G06Onqa zv~$(_Ksk@^fbkQ#W5qi7qV9i|Hf=5U-&MvOYFIZ>kP(6GO5uVMwa+Knq~cjL;mzc{={x!6>6KDRu`OD^?LQ3j3a_ z`tHP~nk#>f2u%qEti``YH>uP+GyEV)>HpWx34{x%s@{gA1D zBskIyWXrk4JcrHF1Xun*S6)f;-r6#x-58)XM>um2pkvWv?0h#}Te6XFKn_ zpl@0^xwcGmwJ6c2$+eM->4^U)HWDw=Mn|4CvZsg@KHtORQ{T5~qwJV6IHp@q1WgfEIt*XIu4E6G!#jY+A~?plPDYJS}3Zh zhD+eY#jP=2bk?)t4|lC5i#C*$vYM|VF^fCODZ6~`N@j5YtAVAMtmY-HdC=5?%w_yn zi{DqY3uU~Um#1~@im7XhLpaueMMxX*C6vNGG=IvxP>z20YI_lsTISJH=1Yd7e;G$8 zta+i_Hq=*wJa0gY{r~d_bin?5LvI#?f`dr?lJN$-19IWy;#Roge#N5~8^9@tx9?W& zzFc=3&fUUeO2<+bD<>BkK^->QVa?tPA~7V;V(Ts>i1AFLimr63J0ceWj6IU zDE_&nr^BDo>BuEExj;ZxNdo_}N4aPq_0Q}p19n`!TZd6_3wMiVA#a6JY7St}n5q~< z_{C9)vF858z9r5iR&loIOG@hK9tFox|3DxVFF+VT=!G%fO`JgviGmL4oD&0YJ?!G;svif+LFhn#wRU7hLAwyA)YKl(%9(iXKL!EPI%u#*Z|xC`(cIW#(-l z=qC{UBuai0Unvr;9)u60XpP*Swu+IV(ZFeyT)Pf6F^h5JJza}5B+(e%*QVy93EhCI z_r+$gdU>hYhFZq0Xi=27zt) z$RZO$5*jCYTbt}|Q9^|!1e2mJAu&={SU3>bj0bhR`L6-+keipkPYjme*Ov6*9DdjM s-cdAhURVMP3=2yP+~8d--pVS%z=*!MY@QkZxW@0J{fHqB8)oKz08s21X8-^I literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_exceptions.cpython-39.pyc b/lib/aiohttp/__pycache__/web_exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a89a5ec7680cb8bbaf2eedab64fea44b9dd30951 GIT binary patch literal 12026 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 S+Ws7wJ)JLb!*<_l&;JhsAnEM@ literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_fileresponse.cpython-39.pyc b/lib/aiohttp/__pycache__/web_fileresponse.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37b6ff6a67cca5e7eb01bcc6be6c04bc5838f694 GIT binary patch literal 6021 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 QF8M1%2W6OF*(%!q4~APo4*&oF literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_middlewares.cpython-39.pyc b/lib/aiohttp/__pycache__/web_middlewares.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da068a94cd5e06039cc9ee189f3f1d5c5c56b250 GIT binary patch literal 3871 zcmZ`+OK%*<5uTpM?sBWhTij>5(ATcpKU@{y|_mU&c zW7XX&n#2+aLIvU_NPt{S1rxn!JKOiw?ntG@oKt2UaN zYFc<6{$r6JO!?L(==4J+V6P zy#)f+N~cy%t$y|BvvB#rXUo5Sxbn+#;_h~o=x8GqWn(!?Q&aO+`#p5Ht2;eW)~Wnm zB;PVf=&-W>Aj)|vWXCS&9;~iDdL(2PcIeVMM_2f|*Q7P%;$ zsMiZ>l1!7Nnkv#4BA5)bNXJ`Yl6Q-sy`^<86ko@pN0xM4c>yZPSrox!lT?g{lw7rnL3o(|Pk#D!ebfc@6mZ<38Qm1yx{FN&W< zn?kKcNkJQ5-xeEVu&SlrPPq`qTOxjTyfMB;Z8e82RSSj1TJ|if*-h54<$Gx3*jQK6 zcuWL2eyC2sFXL6@rJN0{5O4!BdqpG^;V?)ks~Oyg-_h}i!LS( z*|F4Y`gm4`{z5`B5#{Q(>87C%{kxp|*rvh-OSO!Wlx7;hd|ix0FO99bSM+f)RC9## z;g*olOQTqDe`Ci7%ZGcfLa#BkE62kE<*TB|>6>W3o8%mQl>x@GV|JM><3iHyk?t({ zB(9L4u%C4OjRN+XBlL0c_|-vQKehxaiJUGi?k8%b`pwR8g)vW@4BYCR5nv{a)_v3& z-)^}02evgM6;cnoBj^B5 zZ`1e4f@GF5nu?n;b0~-*XO_TpQR&HC;byaF=@n=e6^iFgUN3o=z~^me#Iw{rb<|vFmUDgJ;+M16rL{*-TWDD;?#rkc+@M@T$C`je6OT(YDEv zN>3px(q*l3ywXM*<*5FKNhrz&;-ey8sls->$~CT%v^KFrg}SJse>LAj+9r2`wxQoJ zvI-S6jxV`4ZWTCvNT_0_389pnND9}bn`FK83)IvN6qY^BT-IbWtZBPQAY3-frdb>B z3~R89c-w5org9eZrtRIIj^f_g1aRq-w17LSJ4>_!^q|B*Qg$LIsF*_0sr^t`59WqZ zCWO0mlg+*~DWtqf%Z%ovs!?C!4~5miYqA&XY7>c%&8}QA{qj5w+H!$r|4;5i-r?bO z@G43!Mo9sqq@WA#bH}6uY(UAmyCOfptW$|9$ryRn9W=lo>xdnI#ecoRkg}dI=u0G8 z+;Z{TcNp@kn*jWV#mw(3l$>%dwgPnxb}~?fxX}CSQ*d#4$mqk44_Xs$+;w@cjtA6+JHgKRM57J4&}KX0wZ~{5fi!EAkpX zWlit&khVyIM-sl0ax+P2OIjl7HVPV;g%HzKh7UoT65Uajtt6$Ra*`%a(#SBP7y<>Y zs;BygVwe=1r!FM-JW=oy zeDwdOq7j)kmgSCMFB_j^NC;DLhw^AeP&Sxvx8N-;`q{4MqHH*qTGQUFX9Fnw{^f$9 M^s6!ZUF*Sr0SPb^7XSbN literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_protocol.cpython-39.pyc b/lib/aiohttp/__pycache__/web_protocol.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a7d2fa0aeb1ddd747abbe708d72eb15833ad6de GIT binary patch literal 16505 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 aZ0H{rgxi3`X8M0SxxABia%Mg`V*g*Q0)v47 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_request.cpython-39.pyc b/lib/aiohttp/__pycache__/web_request.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b539556f0902dda6008f416d670be3a4c5492c6 GIT binary patch literal 23967 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 R5!)Bp7vJ|;t@(TT{{r0$70>_x literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_response.cpython-39.pyc b/lib/aiohttp/__pycache__/web_response.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87fd335957b93b68a62f744f836d7099c3678229 GIT binary patch literal 21119 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 RKf@8YI5GlfI@#w>{~y~w>;C`% literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_routedef.cpython-39.pyc b/lib/aiohttp/__pycache__/web_routedef.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..669c223a0a93c0a360cb38937d1d36ad703dff91 GIT binary patch literal 7960 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_runner.cpython-39.pyc b/lib/aiohttp/__pycache__/web_runner.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90f95ae460085bbe0ed8e6c1b3b36fe3f58bc9eb GIT binary patch literal 11660 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 bxtEJ@Ud23<*jy|yci!-5@oQ7hu*`n|yNrN+ literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_urldispatcher.cpython-39.pyc b/lib/aiohttp/__pycache__/web_urldispatcher.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a902df648cc73728d585ec025ea213f75e0fba1b GIT binary patch literal 42111 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 B=~e&$ literal 0 HcmV?d00001 diff --git a/lib/aiohttp/__pycache__/web_ws.cpython-39.pyc b/lib/aiohttp/__pycache__/web_ws.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30e6af68c025fe7bdc75167d02dbff24edcd17b0 GIT binary patch literal 13422 zcmbVTYiu0Xb)MJGKDfJFQWQl}uhq-e+A=NKvMejIBZ;IQevl4HS@uNEX1RAr?Q$Q~ zouL(OmO)?&Nf1D&lBPf!EA28(gQ{%`v`x`AZG*N!+5k;j6hVP>P#{Iy{P3TmK+*hg z8>`!j{dbLP&u?{m*RuX|NUr4kB$SH3EpwmBd@G zq^fBvjXXm{D!tW=mEp8m>8tiz{hW?gc2ozff$C0cCx6E(gVkNuuIi9AR2{a4t0UG( zb+@&)E(@yfyKA?pyQ6P3f&Bi51XL)Jt5-BWqE zdek~v%~`qXBi18~OI9AO9BAG03g?^I>9`ndHtr_+_=)f3hUqDFnX&M3K|w~6kP zxrnEqudR5oE3PdIa~0c*O)a^ldaY3LBD2d&Kp4e>W)2D7q%C_HuIA0IE4j2=`75yEZJV-mXo(X=H?gdnwJdHO9koJm{AvsABPP&$YfQXxccA`B5? zMefAHyiME48=k#9Ju!26>c#Q=*wo}CXg!-hKQ;zp&*pS1Sy*=K`GV`p(wyU&^RnIm z6(UZh?m9(O@A&EXI)}$O4qz*^5iRYiV(YdcRIp#fHo=O9(2&-Jeov*1b=$C_!UU^D z!q}PXR~=hA*Jp~dv~+#kb{5_G()EQUH}Bja&baP^)=yl2u{3wx0are@ zR46Wj51i|TQvHVOE|XrS%DgJ#B5^xyC4lL{awbIz%h?lrrA05klEGI-^x-QNeD#YR_)6Qoc18@` z)3~jjV(@m->I1GHGAKqp&tn9XFNVZ0%I}~&;rBP zxLsU#zsTZy$R4&$9_0aX@U{VoHv&w=j-w?S`yp|dvAY?InKJf>c!;rk+O;1RM;Wsh zb@v6Oa^ey6uwT4lXEBCj81G|Z^mfELAg;ABkBj5L92Bo&q`VPO^29w|JRzP0{*ZXh z&UWBWil=}-EG+v-`5|%Yo@zhr=i5h7^J&cUw0P!r)XL$D#(ze90AqN>ezbf{JSxuK z)9g4}G{v*%<2muc+otuH_%K?aH60b_Fvj!Z0(y8H^~cb~RPS-*%rUJ{cS)lyHNp&wq$)!?l!HeIp3(}pl3$7Or#f#&`iuLLew2k8>tA&r{tIk5+DK+d| zT%Ja^@)-hW2z-FRSpv@zc#gma2|NnW7EUpI#PN_Mq{q1j;3{iMSrw|#?x?ELG@D8u zrU@Vn6F`_HfDxesnhc}DKsv_wIKu=(7$?9dn<`+csR5>&RI9g1}$1Z$^Z& ztjbT}9obE_b})XR8AW<$GX^*)ATo32Bel%SPJ2I@Bg zTzv^8Zxu?eU(ycs9m&EZqi=FU=B5=B-5(-bN^y$g3B~U zHmMtb#LzhY^2kTfTAL%OXS#k*c{>LDdl9JSKd@z?2>b`~>${4f<`TJ`&z2Zl?yHHExNxn0;o5);(%I2iy%&8TUkpBaIvbf62BIu~La zk=m?HLK)NYiFAfi0Xv)#Br-KuCnm^8TqXumL=2Q^#r+Kcai~qs<`R~HY25VUa8+v9 zT+)?HksWB>%bdS3IW_&#`4`zOxjH=|Um$d};H=b&rMj1#^|ccFA99lNA}j&B(xQO; zRXh&+yXqh|H*N98;Vx8Qfwh^$@D-vMCpihe79v7~KeFxVS;OOWa8HINJp)3u)TX+w zUe{K&Ro&Ir6mSo|uYi1)l~tn&y@dU9*5|F30j;Ao^>rP5We|t?<>)Kq9+RYMVWm

bn&UbJsykVcb#QR7E7f#ljkIsB#{q!G~ea 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/_cparser.pxd b/lib/aiohttp/_cparser.pxd new file mode 100644 index 0000000..165dd61 --- /dev/null +++ b/lib/aiohttp/_cparser.pxd @@ -0,0 +1,190 @@ +from libc.stdint cimport ( + int8_t, + int16_t, + int32_t, + int64_t, + uint8_t, + uint16_t, + uint32_t, + uint64_t, +) + + +cdef extern from "../vendor/llhttp/build/llhttp.h": + + struct llhttp__internal_s: + int32_t _index + void* _span_pos0 + void* _span_cb0 + int32_t error + const char* reason + const char* error_pos + void* data + void* _current + uint64_t content_length + uint8_t type + uint8_t method + uint8_t http_major + uint8_t http_minor + uint8_t header_state + uint8_t lenient_flags + uint8_t upgrade + uint8_t finish + uint16_t flags + uint16_t status_code + void* settings + + ctypedef llhttp__internal_s llhttp__internal_t + ctypedef llhttp__internal_t llhttp_t + + ctypedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length) except -1 + ctypedef int (*llhttp_cb)(llhttp_t*) except -1 + + struct llhttp_settings_s: + llhttp_cb on_message_begin + llhttp_data_cb on_url + llhttp_data_cb on_status + llhttp_data_cb on_header_field + llhttp_data_cb on_header_value + llhttp_cb on_headers_complete + llhttp_data_cb on_body + llhttp_cb on_message_complete + llhttp_cb on_chunk_header + llhttp_cb on_chunk_complete + + llhttp_cb on_url_complete + llhttp_cb on_status_complete + llhttp_cb on_header_field_complete + llhttp_cb on_header_value_complete + + ctypedef llhttp_settings_s llhttp_settings_t + + enum llhttp_errno: + HPE_OK, + HPE_INTERNAL, + HPE_STRICT, + HPE_LF_EXPECTED, + HPE_UNEXPECTED_CONTENT_LENGTH, + HPE_CLOSED_CONNECTION, + HPE_INVALID_METHOD, + HPE_INVALID_URL, + HPE_INVALID_CONSTANT, + HPE_INVALID_VERSION, + HPE_INVALID_HEADER_TOKEN, + HPE_INVALID_CONTENT_LENGTH, + HPE_INVALID_CHUNK_SIZE, + HPE_INVALID_STATUS, + HPE_INVALID_EOF_STATE, + HPE_INVALID_TRANSFER_ENCODING, + HPE_CB_MESSAGE_BEGIN, + HPE_CB_HEADERS_COMPLETE, + HPE_CB_MESSAGE_COMPLETE, + HPE_CB_CHUNK_HEADER, + HPE_CB_CHUNK_COMPLETE, + HPE_PAUSED, + HPE_PAUSED_UPGRADE, + HPE_USER + + ctypedef llhttp_errno llhttp_errno_t + + enum llhttp_flags: + F_CONNECTION_KEEP_ALIVE, + F_CONNECTION_CLOSE, + F_CONNECTION_UPGRADE, + F_CHUNKED, + F_UPGRADE, + F_CONTENT_LENGTH, + F_SKIPBODY, + F_TRAILING, + F_TRANSFER_ENCODING + + enum llhttp_lenient_flags: + LENIENT_HEADERS, + LENIENT_CHUNKED_LENGTH + + enum llhttp_type: + HTTP_REQUEST, + HTTP_RESPONSE, + HTTP_BOTH + + enum llhttp_finish_t: + HTTP_FINISH_SAFE, + HTTP_FINISH_SAFE_WITH_CB, + HTTP_FINISH_UNSAFE + + enum llhttp_method: + HTTP_DELETE, + HTTP_GET, + HTTP_HEAD, + HTTP_POST, + HTTP_PUT, + HTTP_CONNECT, + HTTP_OPTIONS, + HTTP_TRACE, + HTTP_COPY, + HTTP_LOCK, + HTTP_MKCOL, + HTTP_MOVE, + HTTP_PROPFIND, + HTTP_PROPPATCH, + HTTP_SEARCH, + HTTP_UNLOCK, + HTTP_BIND, + HTTP_REBIND, + HTTP_UNBIND, + HTTP_ACL, + HTTP_REPORT, + HTTP_MKACTIVITY, + HTTP_CHECKOUT, + HTTP_MERGE, + HTTP_MSEARCH, + HTTP_NOTIFY, + HTTP_SUBSCRIBE, + HTTP_UNSUBSCRIBE, + HTTP_PATCH, + HTTP_PURGE, + HTTP_MKCALENDAR, + HTTP_LINK, + HTTP_UNLINK, + HTTP_SOURCE, + HTTP_PRI, + HTTP_DESCRIBE, + HTTP_ANNOUNCE, + HTTP_SETUP, + HTTP_PLAY, + HTTP_PAUSE, + HTTP_TEARDOWN, + HTTP_GET_PARAMETER, + HTTP_SET_PARAMETER, + HTTP_REDIRECT, + HTTP_RECORD, + HTTP_FLUSH + + ctypedef llhttp_method llhttp_method_t; + + void llhttp_settings_init(llhttp_settings_t* settings) + void llhttp_init(llhttp_t* parser, llhttp_type type, + const llhttp_settings_t* settings) + + llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) + llhttp_errno_t llhttp_finish(llhttp_t* parser) + + int llhttp_message_needs_eof(const llhttp_t* parser) + + int llhttp_should_keep_alive(const llhttp_t* parser) + + void llhttp_pause(llhttp_t* parser) + void llhttp_resume(llhttp_t* parser) + + void llhttp_resume_after_upgrade(llhttp_t* parser) + + llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) + const char* llhttp_get_error_reason(const llhttp_t* parser) + void llhttp_set_error_reason(llhttp_t* parser, const char* reason) + const char* llhttp_get_error_pos(const llhttp_t* parser) + const char* llhttp_errno_name(llhttp_errno_t err) + + const char* llhttp_method_name(llhttp_method_t method) + + void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) + void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) diff --git a/lib/aiohttp/_find_header.pxd b/lib/aiohttp/_find_header.pxd new file mode 100644 index 0000000..37a6c37 --- /dev/null +++ b/lib/aiohttp/_find_header.pxd @@ -0,0 +1,2 @@ +cdef extern from "_find_header.h": + int find_header(char *, int) diff --git a/lib/aiohttp/_headers.pxi b/lib/aiohttp/_headers.pxi new file mode 100644 index 0000000..3744721 --- /dev/null +++ b/lib/aiohttp/_headers.pxi @@ -0,0 +1,83 @@ +# The file is autogenerated from aiohttp/hdrs.py +# Run ./tools/gen.py to update it after the origin changing. + +from . import hdrs +cdef tuple headers = ( + hdrs.ACCEPT, + hdrs.ACCEPT_CHARSET, + hdrs.ACCEPT_ENCODING, + hdrs.ACCEPT_LANGUAGE, + hdrs.ACCEPT_RANGES, + hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS, + hdrs.ACCESS_CONTROL_ALLOW_HEADERS, + hdrs.ACCESS_CONTROL_ALLOW_METHODS, + hdrs.ACCESS_CONTROL_ALLOW_ORIGIN, + hdrs.ACCESS_CONTROL_EXPOSE_HEADERS, + hdrs.ACCESS_CONTROL_MAX_AGE, + hdrs.ACCESS_CONTROL_REQUEST_HEADERS, + hdrs.ACCESS_CONTROL_REQUEST_METHOD, + hdrs.AGE, + hdrs.ALLOW, + hdrs.AUTHORIZATION, + hdrs.CACHE_CONTROL, + hdrs.CONNECTION, + hdrs.CONTENT_DISPOSITION, + hdrs.CONTENT_ENCODING, + hdrs.CONTENT_LANGUAGE, + hdrs.CONTENT_LENGTH, + hdrs.CONTENT_LOCATION, + hdrs.CONTENT_MD5, + hdrs.CONTENT_RANGE, + hdrs.CONTENT_TRANSFER_ENCODING, + hdrs.CONTENT_TYPE, + hdrs.COOKIE, + hdrs.DATE, + hdrs.DESTINATION, + hdrs.DIGEST, + hdrs.ETAG, + hdrs.EXPECT, + hdrs.EXPIRES, + hdrs.FORWARDED, + hdrs.FROM, + hdrs.HOST, + hdrs.IF_MATCH, + hdrs.IF_MODIFIED_SINCE, + hdrs.IF_NONE_MATCH, + hdrs.IF_RANGE, + hdrs.IF_UNMODIFIED_SINCE, + hdrs.KEEP_ALIVE, + hdrs.LAST_EVENT_ID, + hdrs.LAST_MODIFIED, + hdrs.LINK, + hdrs.LOCATION, + hdrs.MAX_FORWARDS, + hdrs.ORIGIN, + hdrs.PRAGMA, + hdrs.PROXY_AUTHENTICATE, + hdrs.PROXY_AUTHORIZATION, + hdrs.RANGE, + hdrs.REFERER, + hdrs.RETRY_AFTER, + hdrs.SEC_WEBSOCKET_ACCEPT, + hdrs.SEC_WEBSOCKET_EXTENSIONS, + hdrs.SEC_WEBSOCKET_KEY, + hdrs.SEC_WEBSOCKET_KEY1, + hdrs.SEC_WEBSOCKET_PROTOCOL, + hdrs.SEC_WEBSOCKET_VERSION, + hdrs.SERVER, + hdrs.SET_COOKIE, + hdrs.TE, + hdrs.TRAILER, + hdrs.TRANSFER_ENCODING, + hdrs.URI, + hdrs.UPGRADE, + hdrs.USER_AGENT, + hdrs.VARY, + hdrs.VIA, + hdrs.WWW_AUTHENTICATE, + hdrs.WANT_DIGEST, + hdrs.WARNING, + hdrs.X_FORWARDED_FOR, + hdrs.X_FORWARDED_HOST, + hdrs.X_FORWARDED_PROTO, +) diff --git a/lib/aiohttp/_helpers.cp39-win_amd64.pyd b/lib/aiohttp/_helpers.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..a9d18fbf96e767f11de29a74d292bb75b5a688a2 GIT binary patch literal 39936 zcmeHw3v^V~_4gf;NruNT!{cH=&_RO)As7f~Fhpla0(W2n5dy>qBq5oQNFL)nASjP$ z5@o!MrBz$3)<$0PZ)>&IDxydN$U{&fRt?f>TB%NqkJMT~wBGNx&pCH86QO=rpEvdGZmekbL`fbbHHgBNDR#{`qnYX}JU0dd!Xg2pt7Nq<9 z%ULkP7+y6`wv*F;ICS9eh8|Q<_ytDyBjk!4JVUNjgWcMUo1Q z6cC>dN%|H+>phY*Qct@8Y^z6KGDbzFN0MxUuM40{6aDTwKQby7SJek?y6cKl8mGdJcRdiJW(IJB+ZD5{*Mg0)IT|cx0;;6 z71l+I9R-d>j>SsDr*@a>wWgm@-_KJ!ke%dG&ns$YUQ>&uaR z6G>H3Z%J~hhY44cHy2aFxvmVSdQ6k==P?urj8#;5#p6`|m`j}j%*UZYO0Y4xUZN81 zN>ht}o}wLBnl#G;Pb1A0GOoZVApBrXa*8Xok)wuW|0d)H{mHiQ&r>9+sbzKI?h_au zU4@GJ7{lrEIvc(-_$!?1EUPPIo^U(WU~WQ%AUX_06;Mo5Lh==q6Ec2P!IM%HwXbq0 zC)q}WMw8=nFKOa{WS%Z%*sQadcBhELael7OPcDQccoX&7e+Bxte~ zWtHF(tD+uN)NB(P|NJkAP(65;AWXl}p|y1Z@n<9k*8IR|)N`&%l5#?J%kD%B0#sOs z3d8@nQIbMFt!pT1NYZQgZ4s*>Nj<2jw6)bLd1wDt;Ta;jKVvgDkupZAL zJjXRTo$)B2W^KyV6yAzWF4dOTblPupW&SlVP*Ig6rRj*@sHm-7e?tsywxizMkCkBF z2ZR^z)Z&u~B~H-{)ZKuP@;gLTmG9%k-xb6ym)nt=0c`-q%=7<-{uiKsmheC>u{U`x zdvt-k_t6WAKMB>kg1<>7GlFh#1z%1k3kCK9koMHd71SLSNr!S*y?_qsTE+=3N%$Ta<3A$3Bq#|l=t)jlAsBeTn zNdq?!kxT6kn3Y}7P^^jA2Lz7E^3ti~ ziV7(zcswTPIFup-!`b8tBB|*FX~oq{D_+y&Ew=(2RvBp%SsE&TfYL~pdTG!~o_?rz z`ckB)pMh)-k$tx~870hEr$ z#s)O#wJHZ8aH#hSPIYdS;`U`~)Jt*i#bAQsUJe^biu*Svv%I~~+-*yl4(F<`hTps% z#6x+Y_lD5gL*cboGp!v*{kxpRJ&MV6EkwNpA`ig0Eu_Afl?YvVfaO8)GL8R269V%{ zxzH!;qm*+xT7aY%bY-2u3oJR+d$|%U{6JAnE+~OZt%E_$hlyDTqndAZ1#M)42I%!a zb~8;av3<9JrN#DyA8BMT(Qni{3Jru}mKNKWE|wOnRT)Twijw%pK)Yc=Nc{7sk?wT9 zg%(8V%>XJ75jxegPMBsfGoC4AsSzoD#N%9wmtj6YylWU!DL&+1We6m(Zaz5JIHB

%&>K2#E=M0y+ExDOG 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 None: ... + def __get__(self, inst: Any, owner: Any) -> Any: ... + def __set__(self, inst: Any, value: Any) -> None: ... diff --git a/lib/aiohttp/_helpers.pyx b/lib/aiohttp/_helpers.pyx new file mode 100644 index 0000000..665f367 --- /dev/null +++ b/lib/aiohttp/_helpers.pyx @@ -0,0 +1,35 @@ +cdef class reify: + """Use as a class method decorator. It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + + """ + + cdef object wrapped + cdef object name + + def __init__(self, wrapped): + self.wrapped = wrapped + self.name = wrapped.__name__ + + @property + def __doc__(self): + return self.wrapped.__doc__ + + def __get__(self, inst, owner): + try: + try: + return inst._cache[self.name] + except KeyError: + val = self.wrapped(inst) + inst._cache[self.name] = val + return val + except AttributeError: + if inst is None: + return self + raise + + def __set__(self, inst, value): + raise AttributeError("reified property is read-only") diff --git a/lib/aiohttp/_http_parser.cp39-win_amd64.pyd b/lib/aiohttp/_http_parser.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..278b8dc5274f59630a2597b1abbcdcd1b1902e65 GIT binary patch literal 215552 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�C!c4vp;J`j8-{S{&z?awUhfFmOVTf7ly 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

<>~a9-#4ngOdsg~1jjpAy#0lOD zrs0g@nVHk*Zu|Pm@>(1$xL{h7Cot{OFC-_;FAoGfbt`L|Gzh2e$DtkeUC>l7d>N2!m$oZySLd#tUA?;w z?dsbVqBwiJNVmWq+DqGg?VH-W+PmBBn^QNZZ_eE8+uXQ${pPmKn>KfB?%dq9xo7jh z=3|@fTT-{Uwv=vZ+|se%YsTD{e`wQcLBtzBEYx7xR* zZqv4DcMqS-sP@vu)?5oyT_CJ2E>qb+mW% zbR6oi?MmI%ylefgwq5PJI#AQDUEPM-rtVJP-L<=WcW8Iop3FV%dph=X?&;doy{BhS z@18?@`u2qO4D3nWo4&VeZ|1(PeZBjR?JM11wtxNpP5XQHAKG7fzwiEI_hZpOltEvj zJ*~a5{TOt$X>&I;)4Tc5=4xo88~Pw^h^=YRM$cBKtZnzU9$DkQZJ})g+m3A$+o|rE z+a24>wpVXokD7OG??TP{wu>D$*`lXX_pY7YJNtHqcG^1Z9qG_QX-8Q{bI1CQj*iZb zUTEW3hmbAq+~wNk+ts*h6SPA52<;l!W#65;J9D>VciHah-RpO^?e2tT4(;ySE%w;< zr0q%H)czt*SEKA?av^N?%}4ejCF>Vj=H!ZzDsn>|}YTL+|t zl68`0uHV)sTkFuaKD3Z+dm5~hY_oBD^Y-@b9ou_gon)KT>l{0rJF0j1cC?}AQE%(p z5!zwfY2TT?GjnI@&a$1&JJ;{**x9+WcjqD4sOZo-(mI?St`1*EqqNp;*lVa`pu-M} z&D`bKRko{I+AY~E^-ZzcwmS{>>e^kpyK#5(?)KdsyL)!`?jG2EYm~WhM&NVlzdEuRZ_;d}VA3|aysUw|grAx@s{@HP8 ztAjrDe&l`8HxOij9&2p&aFk<$VhZ?ihaVgKc#p>@rT~82 zU`Gx)nn_QKZ^>08=LiKU6jXUTp7RR@**SQGFfoB11@yQ>kIh?nJp}ZKp+^QiYUrU+ z(2jymC?Zi%ih5csb1Wr_>9BM8@Nt7zr==C&M();^ i).decode('ascii')) + + +cdef inline str http_method_str(int i): + if i < METHODS_COUNT: + return _http_method[i] + else: + return "" + +cdef inline object find_header(bytes raw_header): + cdef Py_ssize_t size + cdef char *buf + cdef int idx + PyBytes_AsStringAndSize(raw_header, &buf, &size) + idx = _find_header.find_header(buf, size) + if idx == -1: + return raw_header.decode('utf-8', 'surrogateescape') + return headers[idx] + + +@cython.freelist(DEFAULT_FREELIST_SIZE) +cdef class RawRequestMessage: + cdef readonly str method + cdef readonly str path + cdef readonly object version # HttpVersion + cdef readonly object headers # CIMultiDict + cdef readonly object raw_headers # tuple + cdef readonly object should_close + cdef readonly object compression + cdef readonly object upgrade + cdef readonly object chunked + cdef readonly object url # yarl.URL + + def __init__(self, method, path, version, headers, raw_headers, + should_close, compression, upgrade, chunked, url): + self.method = method + self.path = path + self.version = version + self.headers = headers + self.raw_headers = raw_headers + self.should_close = should_close + self.compression = compression + self.upgrade = upgrade + self.chunked = chunked + self.url = url + + def __repr__(self): + info = [] + info.append(("method", self.method)) + info.append(("path", self.path)) + info.append(("version", self.version)) + info.append(("headers", self.headers)) + info.append(("raw_headers", self.raw_headers)) + info.append(("should_close", self.should_close)) + info.append(("compression", self.compression)) + info.append(("upgrade", self.upgrade)) + info.append(("chunked", self.chunked)) + info.append(("url", self.url)) + sinfo = ', '.join(name + '=' + repr(val) for name, val in info) + return '' + + def _replace(self, **dct): + cdef RawRequestMessage ret + ret = _new_request_message(self.method, + self.path, + self.version, + self.headers, + self.raw_headers, + self.should_close, + self.compression, + self.upgrade, + self.chunked, + self.url) + if "method" in dct: + ret.method = dct["method"] + if "path" in dct: + ret.path = dct["path"] + if "version" in dct: + ret.version = dct["version"] + if "headers" in dct: + ret.headers = dct["headers"] + if "raw_headers" in dct: + ret.raw_headers = dct["raw_headers"] + if "should_close" in dct: + ret.should_close = dct["should_close"] + if "compression" in dct: + ret.compression = dct["compression"] + if "upgrade" in dct: + ret.upgrade = dct["upgrade"] + if "chunked" in dct: + ret.chunked = dct["chunked"] + if "url" in dct: + ret.url = dct["url"] + return ret + +cdef _new_request_message(str method, + str path, + object version, + object headers, + object raw_headers, + bint should_close, + object compression, + bint upgrade, + bint chunked, + object url): + cdef RawRequestMessage ret + ret = RawRequestMessage.__new__(RawRequestMessage) + ret.method = method + ret.path = path + ret.version = version + ret.headers = headers + ret.raw_headers = raw_headers + ret.should_close = should_close + ret.compression = compression + ret.upgrade = upgrade + ret.chunked = chunked + ret.url = url + return ret + + +@cython.freelist(DEFAULT_FREELIST_SIZE) +cdef class RawResponseMessage: + cdef readonly object version # HttpVersion + cdef readonly int code + cdef readonly str reason + cdef readonly object headers # CIMultiDict + cdef readonly object raw_headers # tuple + cdef readonly object should_close + cdef readonly object compression + cdef readonly object upgrade + cdef readonly object chunked + + def __init__(self, version, code, reason, headers, raw_headers, + should_close, compression, upgrade, chunked): + self.version = version + self.code = code + self.reason = reason + self.headers = headers + self.raw_headers = raw_headers + self.should_close = should_close + self.compression = compression + self.upgrade = upgrade + self.chunked = chunked + + def __repr__(self): + info = [] + info.append(("version", self.version)) + info.append(("code", self.code)) + info.append(("reason", self.reason)) + info.append(("headers", self.headers)) + info.append(("raw_headers", self.raw_headers)) + info.append(("should_close", self.should_close)) + info.append(("compression", self.compression)) + info.append(("upgrade", self.upgrade)) + info.append(("chunked", self.chunked)) + sinfo = ', '.join(name + '=' + repr(val) for name, val in info) + return '' + + +cdef _new_response_message(object version, + int code, + str reason, + object headers, + object raw_headers, + bint should_close, + object compression, + bint upgrade, + bint chunked): + cdef RawResponseMessage ret + ret = RawResponseMessage.__new__(RawResponseMessage) + ret.version = version + ret.code = code + ret.reason = reason + ret.headers = headers + ret.raw_headers = raw_headers + ret.should_close = should_close + ret.compression = compression + ret.upgrade = upgrade + ret.chunked = chunked + return ret + + +@cython.internal +cdef class HttpParser: + + cdef: + cparser.llhttp_t* _cparser + cparser.llhttp_settings_t* _csettings + + bytearray _raw_name + bytearray _raw_value + bint _has_value + + object _protocol + object _loop + object _timer + + size_t _max_line_size + size_t _max_field_size + size_t _max_headers + bint _response_with_body + bint _read_until_eof + + bint _started + object _url + bytearray _buf + str _path + str _reason + object _headers + list _raw_headers + bint _upgraded + list _messages + object _payload + bint _payload_error + object _payload_exception + object _last_error + bint _auto_decompress + int _limit + + str _content_encoding + + Py_buffer py_buf + + def __cinit__(self): + self._cparser = \ + PyMem_Malloc(sizeof(cparser.llhttp_t)) + if self._cparser is NULL: + raise MemoryError() + + self._csettings = \ + PyMem_Malloc(sizeof(cparser.llhttp_settings_t)) + if self._csettings is NULL: + raise MemoryError() + + def __dealloc__(self): + PyMem_Free(self._cparser) + PyMem_Free(self._csettings) + + cdef _init( + self, cparser.llhttp_type mode, + object protocol, object loop, int limit, + object timer=None, + size_t max_line_size=8190, size_t max_headers=32768, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True, + ): + cparser.llhttp_settings_init(self._csettings) + cparser.llhttp_init(self._cparser, mode, self._csettings) + self._cparser.data = self + self._cparser.content_length = 0 + + self._protocol = protocol + self._loop = loop + self._timer = timer + + self._buf = bytearray() + self._payload = None + self._payload_error = 0 + self._payload_exception = payload_exception + self._messages = [] + + self._raw_name = bytearray() + self._raw_value = bytearray() + self._has_value = False + + self._max_line_size = max_line_size + self._max_headers = max_headers + self._max_field_size = max_field_size + self._response_with_body = response_with_body + self._read_until_eof = read_until_eof + self._upgraded = False + self._auto_decompress = auto_decompress + self._content_encoding = None + + self._csettings.on_url = cb_on_url + self._csettings.on_status = cb_on_status + self._csettings.on_header_field = cb_on_header_field + self._csettings.on_header_value = cb_on_header_value + self._csettings.on_headers_complete = cb_on_headers_complete + self._csettings.on_body = cb_on_body + self._csettings.on_message_begin = cb_on_message_begin + self._csettings.on_message_complete = cb_on_message_complete + self._csettings.on_chunk_header = cb_on_chunk_header + self._csettings.on_chunk_complete = cb_on_chunk_complete + + self._last_error = None + self._limit = limit + + cdef _process_header(self): + if self._raw_name: + raw_name = bytes(self._raw_name) + raw_value = bytes(self._raw_value) + + name = find_header(raw_name) + value = raw_value.decode('utf-8', 'surrogateescape') + + self._headers.add(name, value) + + if name is CONTENT_ENCODING: + self._content_encoding = value + + PyByteArray_Resize(self._raw_name, 0) + PyByteArray_Resize(self._raw_value, 0) + self._has_value = False + self._raw_headers.append((raw_name, raw_value)) + + cdef _on_header_field(self, char* at, size_t length): + cdef Py_ssize_t size + cdef char *buf + if self._has_value: + self._process_header() + + size = PyByteArray_Size(self._raw_name) + PyByteArray_Resize(self._raw_name, size + length) + buf = PyByteArray_AsString(self._raw_name) + memcpy(buf + size, at, length) + + cdef _on_header_value(self, char* at, size_t length): + cdef Py_ssize_t size + cdef char *buf + + size = PyByteArray_Size(self._raw_value) + PyByteArray_Resize(self._raw_value, size + length) + buf = PyByteArray_AsString(self._raw_value) + memcpy(buf + size, at, length) + self._has_value = True + + cdef _on_headers_complete(self): + self._process_header() + + method = http_method_str(self._cparser.method) + should_close = not cparser.llhttp_should_keep_alive(self._cparser) + upgrade = self._cparser.upgrade + chunked = self._cparser.flags & cparser.F_CHUNKED + + raw_headers = tuple(self._raw_headers) + headers = CIMultiDictProxy(self._headers) + + if upgrade or self._cparser.method == cparser.HTTP_CONNECT: + self._upgraded = True + + # do not support old websocket spec + if SEC_WEBSOCKET_KEY1 in headers: + raise InvalidHeader(SEC_WEBSOCKET_KEY1) + + encoding = None + enc = self._content_encoding + if enc is not None: + self._content_encoding = None + enc = enc.lower() + if enc in ('gzip', 'deflate', 'br'): + encoding = enc + + if self._cparser.type == cparser.HTTP_REQUEST: + msg = _new_request_message( + method, self._path, + self.http_version(), headers, raw_headers, + should_close, encoding, upgrade, chunked, self._url) + else: + msg = _new_response_message( + self.http_version(), self._cparser.status_code, self._reason, + headers, raw_headers, should_close, encoding, + upgrade, chunked) + + if ( + ULLONG_MAX > self._cparser.content_length > 0 or chunked or + self._cparser.method == cparser.HTTP_CONNECT or + (self._cparser.status_code >= 199 and + self._cparser.content_length == 0 and + self._read_until_eof) + ): + payload = StreamReader( + self._protocol, timer=self._timer, loop=self._loop, + limit=self._limit) + else: + payload = EMPTY_PAYLOAD + + self._payload = payload + if encoding is not None and self._auto_decompress: + self._payload = DeflateBuffer(payload, encoding) + + if not self._response_with_body: + payload = EMPTY_PAYLOAD + + self._messages.append((msg, payload)) + + cdef _on_message_complete(self): + self._payload.feed_eof() + self._payload = None + + cdef _on_chunk_header(self): + self._payload.begin_http_chunk_receiving() + + cdef _on_chunk_complete(self): + self._payload.end_http_chunk_receiving() + + cdef object _on_status_complete(self): + pass + + cdef inline http_version(self): + cdef cparser.llhttp_t* parser = self._cparser + + if parser.http_major == 1: + if parser.http_minor == 0: + return HttpVersion10 + elif parser.http_minor == 1: + return HttpVersion11 + + return HttpVersion(parser.http_major, parser.http_minor) + + ### Public API ### + + def feed_eof(self): + cdef bytes desc + + if self._payload is not None: + if self._cparser.flags & cparser.F_CHUNKED: + raise TransferEncodingError( + "Not enough data for satisfy transfer length header.") + elif self._cparser.flags & cparser.F_CONTENT_LENGTH: + raise ContentLengthError( + "Not enough data for satisfy content length header.") + elif cparser.llhttp_get_errno(self._cparser) != cparser.HPE_OK: + desc = cparser.llhttp_get_error_reason(self._cparser) + raise PayloadEncodingError(desc.decode('latin-1')) + else: + self._payload.feed_eof() + elif self._started: + self._on_headers_complete() + if self._messages: + return self._messages[-1][0] + + def feed_data(self, data): + cdef: + size_t data_len + size_t nb + cdef cparser.llhttp_errno_t errno + + PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE) + data_len = self.py_buf.len + + errno = cparser.llhttp_execute( + self._cparser, + self.py_buf.buf, + data_len) + + if errno is cparser.HPE_PAUSED_UPGRADE: + cparser.llhttp_resume_after_upgrade(self._cparser) + + nb = cparser.llhttp_get_error_pos(self._cparser) - self.py_buf.buf + + PyBuffer_Release(&self.py_buf) + + if errno not in (cparser.HPE_OK, cparser.HPE_PAUSED_UPGRADE): + if self._payload_error == 0: + if self._last_error is not None: + ex = self._last_error + self._last_error = None + else: + ex = parser_error_from_errno(self._cparser) + self._payload = None + raise ex + + if self._messages: + messages = self._messages + self._messages = [] + else: + messages = () + + if self._upgraded: + return messages, True, data[nb:] + else: + return messages, False, b'' + + def set_upgraded(self, val): + self._upgraded = val + + +cdef class HttpRequestParser(HttpParser): + + def __init__( + self, protocol, loop, int limit, timer=None, + size_t max_line_size=8190, size_t max_headers=32768, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True, + ): + self._init(cparser.HTTP_REQUEST, protocol, loop, limit, timer, + max_line_size, max_headers, max_field_size, + payload_exception, response_with_body, read_until_eof, + auto_decompress) + + cdef object _on_status_complete(self): + cdef int idx1, idx2 + if not self._buf: + return + self._path = self._buf.decode('utf-8', 'surrogateescape') + try: + idx3 = len(self._path) + if self._cparser.method == cparser.HTTP_CONNECT: + # authority-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3 + self._url = URL.build(authority=self._path, encoded=True) + elif idx3 > 1 and self._path[0] == '/': + # origin-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1 + idx1 = self._path.find("?") + if idx1 == -1: + query = "" + idx2 = self._path.find("#") + if idx2 == -1: + path = self._path + fragment = "" + else: + path = self._path[0: idx2] + fragment = self._path[idx2+1:] + + else: + path = self._path[0:idx1] + idx1 += 1 + idx2 = self._path.find("#", idx1+1) + if idx2 == -1: + query = self._path[idx1:] + fragment = "" + else: + query = self._path[idx1: idx2] + fragment = self._path[idx2+1:] + + self._url = URL.build( + path=path, + query_string=query, + fragment=fragment, + encoded=True, + ) + else: + # absolute-form for proxy maybe, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2 + self._url = URL(self._path, encoded=True) + finally: + PyByteArray_Resize(self._buf, 0) + + +cdef class HttpResponseParser(HttpParser): + + def __init__( + self, protocol, loop, int limit, timer=None, + size_t max_line_size=8190, size_t max_headers=32768, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True + ): + self._init(cparser.HTTP_RESPONSE, protocol, loop, limit, timer, + max_line_size, max_headers, max_field_size, + payload_exception, response_with_body, read_until_eof, + auto_decompress) + + cdef object _on_status_complete(self): + if self._buf: + self._reason = self._buf.decode('utf-8', 'surrogateescape') + PyByteArray_Resize(self._buf, 0) + else: + self._reason = self._reason or '' + +cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + + pyparser._started = True + pyparser._headers = CIMultiDict() + pyparser._raw_headers = [] + PyByteArray_Resize(pyparser._buf, 0) + pyparser._path = None + pyparser._reason = None + return 0 + + +cdef int cb_on_url(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + try: + if length > pyparser._max_line_size: + raise LineTooLong( + 'Status line is too long', pyparser._max_line_size, length) + extend(pyparser._buf, at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_status(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef str reason + try: + if length > pyparser._max_line_size: + raise LineTooLong( + 'Status line is too long', pyparser._max_line_size, length) + extend(pyparser._buf, at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_header_field(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef Py_ssize_t size + try: + pyparser._on_status_complete() + size = len(pyparser._raw_name) + length + if size > pyparser._max_field_size: + raise LineTooLong( + 'Header name is too long', pyparser._max_field_size, size) + pyparser._on_header_field(at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_header_value(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef Py_ssize_t size + try: + size = len(pyparser._raw_value) + length + if size > pyparser._max_field_size: + raise LineTooLong( + 'Header value is too long', pyparser._max_field_size, size) + pyparser._on_header_value(at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_status_complete() + pyparser._on_headers_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + if ( + pyparser._cparser.upgrade or + pyparser._cparser.method == cparser.HTTP_CONNECT + ): + return 2 + else: + return 0 + + +cdef int cb_on_body(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef bytes body = at[:length] + try: + pyparser._payload.feed_data(body, length) + except BaseException as exc: + if pyparser._payload_exception is not None: + pyparser._payload.set_exception(pyparser._payload_exception(str(exc))) + else: + pyparser._payload.set_exception(exc) + pyparser._payload_error = 1 + return -1 + else: + return 0 + + +cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._started = False + pyparser._on_message_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_chunk_header() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_chunk_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef parser_error_from_errno(cparser.llhttp_t* parser): + cdef cparser.llhttp_errno_t errno = cparser.llhttp_get_errno(parser) + cdef bytes desc = cparser.llhttp_get_error_reason(parser) + + if errno in (cparser.HPE_CB_MESSAGE_BEGIN, + cparser.HPE_CB_HEADERS_COMPLETE, + cparser.HPE_CB_MESSAGE_COMPLETE, + cparser.HPE_CB_CHUNK_HEADER, + cparser.HPE_CB_CHUNK_COMPLETE, + cparser.HPE_INVALID_CONSTANT, + cparser.HPE_INVALID_HEADER_TOKEN, + cparser.HPE_INVALID_CONTENT_LENGTH, + cparser.HPE_INVALID_CHUNK_SIZE, + cparser.HPE_INVALID_EOF_STATE, + cparser.HPE_INVALID_TRANSFER_ENCODING): + cls = BadHttpMessage + + elif errno == cparser.HPE_INVALID_STATUS: + cls = BadStatusLine + + elif errno == cparser.HPE_INVALID_METHOD: + cls = BadStatusLine + + elif errno == cparser.HPE_INVALID_VERSION: + cls = BadStatusLine + + elif errno == cparser.HPE_INVALID_URL: + cls = InvalidURLError + + else: + cls = BadHttpMessage + + return cls(desc.decode('latin-1')) diff --git a/lib/aiohttp/_http_writer.cp39-win_amd64.pyd b/lib/aiohttp/_http_writer.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..72f6ca2f307599ad24de5797d0c5c479e2304e11 GIT binary patch literal 35840 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 ay?ncG`=;%ix9{2Bvi*N_EB&`s{(k{g56CJ2 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/_http_writer.pyx b/lib/aiohttp/_http_writer.pyx new file mode 100644 index 0000000..eff8521 --- /dev/null +++ b/lib/aiohttp/_http_writer.pyx @@ -0,0 +1,163 @@ +from cpython.bytes cimport PyBytes_FromStringAndSize +from cpython.exc cimport PyErr_NoMemory +from cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc +from cpython.object cimport PyObject_Str +from libc.stdint cimport uint8_t, uint64_t +from libc.string cimport memcpy + +from multidict import istr + +DEF BUF_SIZE = 16 * 1024 # 16KiB +cdef char BUFFER[BUF_SIZE] + +cdef object _istr = istr + + +# ----------------- writer --------------------------- + +cdef struct Writer: + char *buf + Py_ssize_t size + Py_ssize_t pos + + +cdef inline void _init_writer(Writer* writer): + writer.buf = &BUFFER[0] + writer.size = BUF_SIZE + writer.pos = 0 + + +cdef inline void _release_writer(Writer* writer): + if writer.buf != BUFFER: + PyMem_Free(writer.buf) + + +cdef inline int _write_byte(Writer* writer, uint8_t ch): + cdef char * buf + cdef Py_ssize_t size + + if writer.pos == writer.size: + # reallocate + size = writer.size + BUF_SIZE + if writer.buf == BUFFER: + buf = PyMem_Malloc(size) + if buf == NULL: + PyErr_NoMemory() + return -1 + memcpy(buf, writer.buf, writer.size) + else: + buf = PyMem_Realloc(writer.buf, size) + if buf == NULL: + PyErr_NoMemory() + return -1 + writer.buf = buf + writer.size = size + writer.buf[writer.pos] = ch + writer.pos += 1 + return 0 + + +cdef inline int _write_utf8(Writer* writer, Py_UCS4 symbol): + cdef uint64_t utf = symbol + + if utf < 0x80: + return _write_byte(writer, utf) + elif utf < 0x800: + if _write_byte(writer, (0xc0 | (utf >> 6))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + elif 0xD800 <= utf <= 0xDFFF: + # surogate pair, ignored + return 0 + elif utf < 0x10000: + if _write_byte(writer, (0xe0 | (utf >> 12))) < 0: + return -1 + if _write_byte(writer, (0x80 | ((utf >> 6) & 0x3f))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + elif utf > 0x10FFFF: + # symbol is too large + return 0 + else: + if _write_byte(writer, (0xf0 | (utf >> 18))) < 0: + return -1 + if _write_byte(writer, + (0x80 | ((utf >> 12) & 0x3f))) < 0: + return -1 + if _write_byte(writer, + (0x80 | ((utf >> 6) & 0x3f))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + + +cdef inline int _write_str(Writer* writer, str s): + cdef Py_UCS4 ch + for ch in s: + if _write_utf8(writer, ch) < 0: + return -1 + + +# --------------- _serialize_headers ---------------------- + +cdef str to_str(object s): + typ = type(s) + if typ is str: + return s + elif typ is _istr: + return PyObject_Str(s) + elif not isinstance(s, str): + raise TypeError("Cannot serialize non-str key {!r}".format(s)) + else: + return str(s) + + +cdef void _safe_header(str string) except *: + if "\r" in string or "\n" in string: + raise ValueError( + "Newline or carriage return character detected in HTTP status message or " + "header. This is a potential security issue." + ) + + +def _serialize_headers(str status_line, headers): + cdef Writer writer + cdef object key + cdef object val + cdef bytes ret + + _init_writer(&writer) + + for key, val in headers.items(): + _safe_header(to_str(key)) + _safe_header(to_str(val)) + + try: + if _write_str(&writer, status_line) < 0: + raise + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + for key, val in headers.items(): + if _write_str(&writer, to_str(key)) < 0: + raise + if _write_byte(&writer, b':') < 0: + raise + if _write_byte(&writer, b' ') < 0: + raise + if _write_str(&writer, to_str(val)) < 0: + raise + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + return PyBytes_FromStringAndSize(writer.buf, writer.pos) + finally: + _release_writer(&writer) diff --git a/lib/aiohttp/_websocket.cp39-win_amd64.pyd b/lib/aiohttp/_websocket.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..d9261d2bb92cc5b338628d5f0c85a2c2df1c3338 GIT binary patch literal 24064 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 literal 0 HcmV?d00001 diff --git a/lib/aiohttp/_websocket.pyx b/lib/aiohttp/_websocket.pyx new file mode 100644 index 0000000..94318d2 --- /dev/null +++ b/lib/aiohttp/_websocket.pyx @@ -0,0 +1,56 @@ +from cpython cimport PyBytes_AsString + + +#from cpython cimport PyByteArray_AsString # cython still not exports that +cdef extern from "Python.h": + char* PyByteArray_AsString(bytearray ba) except NULL + +from libc.stdint cimport uint32_t, uint64_t, uintmax_t + + +def _websocket_mask_cython(object mask, object data): + """Note, this function mutates its `data` argument + """ + cdef: + Py_ssize_t data_len, i + # bit operations on signed integers are implementation-specific + unsigned char * in_buf + const unsigned char * mask_buf + uint32_t uint32_msk + uint64_t uint64_msk + + assert len(mask) == 4 + + if not isinstance(mask, bytes): + mask = bytes(mask) + + if isinstance(data, bytearray): + data = data + else: + data = bytearray(data) + + data_len = len(data) + in_buf = PyByteArray_AsString(data) + mask_buf = PyBytes_AsString(mask) + uint32_msk = (mask_buf)[0] + + # TODO: align in_data ptr to achieve even faster speeds + # does it need in python ?! malloc() always aligns to sizeof(long) bytes + + if sizeof(size_t) >= 8: + uint64_msk = uint32_msk + uint64_msk = (uint64_msk << 32) | uint32_msk + + while data_len >= 8: + (in_buf)[0] ^= uint64_msk + in_buf += 8 + data_len -= 8 + + + while data_len >= 4: + (in_buf)[0] ^= uint32_msk + in_buf += 4 + data_len -= 4 + + for i in range(0, data_len): + in_buf[i] ^= mask_buf[i] diff --git a/lib/aiohttp/abc.py b/lib/aiohttp/abc.py new file mode 100644 index 0000000..44a3bda --- /dev/null +++ b/lib/aiohttp/abc.py @@ -0,0 +1,207 @@ +import asyncio +import logging +from abc import ABC, abstractmethod +from collections.abc import Sized +from http.cookies import BaseCookie, Morsel +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Dict, + Generator, + Iterable, + List, + Optional, + Tuple, +) + +from multidict import CIMultiDict +from yarl import URL + +from .helpers import get_running_loop +from .typedefs import LooseCookies + +if TYPE_CHECKING: # pragma: no cover + from .web_app import Application + from .web_exceptions import HTTPException + from .web_request import BaseRequest, Request + from .web_response import StreamResponse +else: + BaseRequest = Request = Application = StreamResponse = None + HTTPException = None + + +class AbstractRouter(ABC): + def __init__(self) -> None: + self._frozen = False + + def post_init(self, app: Application) -> None: + """Post init stage. + + Not an abstract method for sake of backward compatibility, + but if the router wants to be aware of the application + it can override this. + """ + + @property + def frozen(self) -> bool: + return self._frozen + + def freeze(self) -> None: + """Freeze router.""" + self._frozen = True + + @abstractmethod + async def resolve(self, request: Request) -> "AbstractMatchInfo": + """Return MATCH_INFO for given request""" + + +class AbstractMatchInfo(ABC): + @property # pragma: no branch + @abstractmethod + def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]: + """Execute matched request handler""" + + @property + @abstractmethod + def expect_handler(self) -> Callable[[Request], Awaitable[None]]: + """Expect handler for 100-continue processing""" + + @property # pragma: no branch + @abstractmethod + def http_exception(self) -> Optional[HTTPException]: + """HTTPException instance raised on router's resolving, or None""" + + @abstractmethod # pragma: no branch + def get_info(self) -> Dict[str, Any]: + """Return a dict with additional info useful for introspection""" + + @property # pragma: no branch + @abstractmethod + def apps(self) -> Tuple[Application, ...]: + """Stack of nested applications. + + Top level application is left-most element. + + """ + + @abstractmethod + def add_app(self, app: Application) -> None: + """Add application to the nested apps stack.""" + + @abstractmethod + def freeze(self) -> None: + """Freeze the match info. + + The method is called after route resolution. + + After the call .add_app() is forbidden. + + """ + + +class AbstractView(ABC): + """Abstract class based view.""" + + def __init__(self, request: Request) -> None: + self._request = request + + @property + def request(self) -> Request: + """Request instance.""" + return self._request + + @abstractmethod + def __await__(self) -> Generator[Any, None, StreamResponse]: + """Execute the view handler.""" + + +class AbstractResolver(ABC): + """Abstract DNS resolver.""" + + @abstractmethod + async def resolve(self, host: str, port: int, family: int) -> List[Dict[str, Any]]: + """Return IP address for given hostname""" + + @abstractmethod + async def close(self) -> None: + """Release resolver""" + + +if TYPE_CHECKING: # pragma: no cover + IterableBase = Iterable[Morsel[str]] +else: + IterableBase = Iterable + + +ClearCookiePredicate = Callable[["Morsel[str]"], bool] + + +class AbstractCookieJar(Sized, IterableBase): + """Abstract Cookie Jar.""" + + def __init__(self, *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: + self._loop = get_running_loop(loop) + + @abstractmethod + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + """Clear all cookies if no predicate is passed.""" + + @abstractmethod + def clear_domain(self, domain: str) -> None: + """Clear all cookies for domain and all subdomains.""" + + @abstractmethod + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + """Update cookies.""" + + @abstractmethod + def filter_cookies(self, request_url: URL) -> "BaseCookie[str]": + """Return the jar's cookies filtered by their attributes.""" + + +class AbstractStreamWriter(ABC): + """Abstract stream writer.""" + + buffer_size = 0 + output_size = 0 + length: Optional[int] = 0 + + @abstractmethod + async def write(self, chunk: bytes) -> None: + """Write chunk into stream.""" + + @abstractmethod + async def write_eof(self, chunk: bytes = b"") -> None: + """Write last chunk.""" + + @abstractmethod + async def drain(self) -> None: + """Flush the write buffer.""" + + @abstractmethod + def enable_compression(self, encoding: str = "deflate") -> None: + """Enable HTTP body compression""" + + @abstractmethod + def enable_chunking(self) -> None: + """Enable HTTP chunked mode""" + + @abstractmethod + async def write_headers( + self, status_line: str, headers: "CIMultiDict[str]" + ) -> None: + """Write HTTP headers""" + + +class AbstractAccessLogger(ABC): + """Abstract writer to access log.""" + + def __init__(self, logger: logging.Logger, log_format: str) -> None: + self.logger = logger + self.log_format = log_format + + @abstractmethod + def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None: + """Emit log to logger.""" diff --git a/lib/aiohttp/base_protocol.py b/lib/aiohttp/base_protocol.py new file mode 100644 index 0000000..4c9f0a7 --- /dev/null +++ b/lib/aiohttp/base_protocol.py @@ -0,0 +1,90 @@ +import asyncio +from typing import Optional, cast + +from .tcp_helpers import tcp_nodelay + + +class BaseProtocol(asyncio.Protocol): + __slots__ = ( + "_loop", + "_paused", + "_drain_waiter", + "_connection_lost", + "_reading_paused", + "transport", + ) + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop: asyncio.AbstractEventLoop = loop + self._paused = False + self._drain_waiter: Optional[asyncio.Future[None]] = None + self._reading_paused = False + + self.transport: Optional[asyncio.Transport] = None + + @property + def connected(self) -> bool: + """Return True if the connection is open.""" + return self.transport is not None + + def pause_writing(self) -> None: + assert not self._paused + self._paused = True + + def resume_writing(self) -> None: + assert self._paused + self._paused = False + + waiter = self._drain_waiter + if waiter is not None: + self._drain_waiter = None + if not waiter.done(): + waiter.set_result(None) + + def pause_reading(self) -> None: + if not self._reading_paused and self.transport is not None: + try: + self.transport.pause_reading() + except (AttributeError, NotImplementedError, RuntimeError): + pass + self._reading_paused = True + + def resume_reading(self) -> None: + if self._reading_paused and self.transport is not None: + try: + self.transport.resume_reading() + except (AttributeError, NotImplementedError, RuntimeError): + pass + self._reading_paused = False + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + tr = cast(asyncio.Transport, transport) + tcp_nodelay(tr, True) + self.transport = tr + + def connection_lost(self, exc: Optional[BaseException]) -> None: + # Wake up the writer if currently paused. + self.transport = None + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + return + self._drain_waiter = None + if waiter.done(): + return + if exc is None: + waiter.set_result(None) + else: + waiter.set_exception(exc) + + async def _drain_helper(self) -> None: + if not self.connected: + raise ConnectionResetError("Connection lost") + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + waiter = self._loop.create_future() + self._drain_waiter = waiter + await asyncio.shield(waiter) diff --git a/lib/aiohttp/client.py b/lib/aiohttp/client.py new file mode 100644 index 0000000..0d0f4c1 --- /dev/null +++ b/lib/aiohttp/client.py @@ -0,0 +1,1305 @@ +"""HTTP Client for asyncio.""" + +import asyncio +import base64 +import hashlib +import json +import os +import sys +import traceback +import warnings +from contextlib import suppress +from types import SimpleNamespace, TracebackType +from typing import ( + Any, + Awaitable, + Callable, + Coroutine, + FrozenSet, + Generator, + Generic, + Iterable, + List, + Mapping, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, +) + +import attr +from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr +from yarl import URL + +from . import hdrs, http, payload +from .abc import AbstractCookieJar +from .client_exceptions import ( + ClientConnectionError as ClientConnectionError, + ClientConnectorCertificateError as ClientConnectorCertificateError, + ClientConnectorError as ClientConnectorError, + ClientConnectorSSLError as ClientConnectorSSLError, + ClientError as ClientError, + ClientHttpProxyError as ClientHttpProxyError, + ClientOSError as ClientOSError, + ClientPayloadError as ClientPayloadError, + ClientProxyConnectionError as ClientProxyConnectionError, + ClientResponseError as ClientResponseError, + ClientSSLError as ClientSSLError, + ContentTypeError as ContentTypeError, + InvalidURL as InvalidURL, + ServerConnectionError as ServerConnectionError, + ServerDisconnectedError as ServerDisconnectedError, + ServerFingerprintMismatch as ServerFingerprintMismatch, + ServerTimeoutError as ServerTimeoutError, + TooManyRedirects as TooManyRedirects, + WSServerHandshakeError as WSServerHandshakeError, +) +from .client_reqrep import ( + ClientRequest as ClientRequest, + ClientResponse as ClientResponse, + Fingerprint as Fingerprint, + RequestInfo as RequestInfo, + _merge_ssl_params, +) +from .client_ws import ClientWebSocketResponse as ClientWebSocketResponse +from .connector import ( + BaseConnector as BaseConnector, + NamedPipeConnector as NamedPipeConnector, + TCPConnector as TCPConnector, + UnixConnector as UnixConnector, +) +from .cookiejar import CookieJar +from .helpers import ( + DEBUG, + PY_36, + BasicAuth, + TimeoutHandle, + ceil_timeout, + get_env_proxy_for_url, + get_running_loop, + sentinel, + strip_auth_from_url, +) +from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter +from .http_websocket import WSHandshakeError, WSMessage, ws_ext_gen, ws_ext_parse +from .streams import FlowControlDataQueue +from .tracing import Trace, TraceConfig +from .typedefs import Final, JSONEncoder, LooseCookies, LooseHeaders, StrOrURL + +__all__ = ( + # client_exceptions + "ClientConnectionError", + "ClientConnectorCertificateError", + "ClientConnectorError", + "ClientConnectorSSLError", + "ClientError", + "ClientHttpProxyError", + "ClientOSError", + "ClientPayloadError", + "ClientProxyConnectionError", + "ClientResponseError", + "ClientSSLError", + "ContentTypeError", + "InvalidURL", + "ServerConnectionError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ServerTimeoutError", + "TooManyRedirects", + "WSServerHandshakeError", + # client_reqrep + "ClientRequest", + "ClientResponse", + "Fingerprint", + "RequestInfo", + # connector + "BaseConnector", + "TCPConnector", + "UnixConnector", + "NamedPipeConnector", + # client_ws + "ClientWebSocketResponse", + # client + "ClientSession", + "ClientTimeout", + "request", +) + + +try: + from ssl import SSLContext +except ImportError: # pragma: no cover + SSLContext = object # type: ignore[misc,assignment] + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class ClientTimeout: + total: Optional[float] = None + connect: Optional[float] = None + sock_read: Optional[float] = None + sock_connect: Optional[float] = None + + # pool_queue_timeout: Optional[float] = None + # dns_resolution_timeout: Optional[float] = None + # socket_connect_timeout: Optional[float] = None + # connection_acquiring_timeout: Optional[float] = None + # new_connection_timeout: Optional[float] = None + # http_header_timeout: Optional[float] = None + # response_body_timeout: Optional[float] = None + + # to create a timeout specific for a single request, either + # - create a completely new one to overwrite the default + # - or use http://www.attrs.org/en/stable/api.html#attr.evolve + # to overwrite the defaults + + +# 5 Minute default read timeout +DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60) + +_RetType = TypeVar("_RetType") + + +class ClientSession: + """First-class interface for making HTTP requests.""" + + ATTRS = frozenset( + [ + "_base_url", + "_source_traceback", + "_connector", + "requote_redirect_url", + "_loop", + "_cookie_jar", + "_connector_owner", + "_default_auth", + "_version", + "_json_serialize", + "_requote_redirect_url", + "_timeout", + "_raise_for_status", + "_auto_decompress", + "_trust_env", + "_default_headers", + "_skip_auto_headers", + "_request_class", + "_response_class", + "_ws_response_class", + "_trace_configs", + "_read_bufsize", + ] + ) + + _source_traceback = None # type: Optional[traceback.StackSummary] + _connector = None # type: Optional[BaseConnector] + + def __init__( + self, + base_url: Optional[StrOrURL] = None, + *, + connector: Optional[BaseConnector] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + cookies: Optional[LooseCookies] = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + auth: Optional[BasicAuth] = None, + json_serialize: JSONEncoder = json.dumps, + request_class: Type[ClientRequest] = ClientRequest, + response_class: Type[ClientResponse] = ClientResponse, + ws_response_class: Type[ClientWebSocketResponse] = ClientWebSocketResponse, + version: HttpVersion = http.HttpVersion11, + cookie_jar: Optional[AbstractCookieJar] = None, + connector_owner: bool = True, + raise_for_status: bool = False, + read_timeout: Union[float, object] = sentinel, + conn_timeout: Optional[float] = None, + timeout: Union[object, ClientTimeout] = sentinel, + auto_decompress: bool = True, + trust_env: bool = False, + requote_redirect_url: bool = True, + trace_configs: Optional[List[TraceConfig]] = None, + read_bufsize: int = 2**16, + ) -> None: + if loop is None: + if connector is not None: + loop = connector._loop + + loop = get_running_loop(loop) + + if base_url is None or isinstance(base_url, URL): + self._base_url: Optional[URL] = base_url + else: + self._base_url = URL(base_url) + assert ( + self._base_url.origin() == self._base_url + ), "Only absolute URLs without path part are supported" + + if connector is None: + connector = TCPConnector(loop=loop) + + if connector._loop is not loop: + raise RuntimeError("Session and connector has to use same event loop") + + self._loop = loop + + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + if cookie_jar is None: + cookie_jar = CookieJar(loop=loop) + self._cookie_jar = cookie_jar + + if cookies is not None: + self._cookie_jar.update_cookies(cookies) + + self._connector = connector + self._connector_owner = connector_owner + self._default_auth = auth + self._version = version + self._json_serialize = json_serialize + if timeout is sentinel: + self._timeout = DEFAULT_TIMEOUT + if read_timeout is not sentinel: + warnings.warn( + "read_timeout is deprecated, " "use timeout argument instead", + DeprecationWarning, + stacklevel=2, + ) + self._timeout = attr.evolve(self._timeout, total=read_timeout) + if conn_timeout is not None: + self._timeout = attr.evolve(self._timeout, connect=conn_timeout) + warnings.warn( + "conn_timeout is deprecated, " "use timeout argument instead", + DeprecationWarning, + stacklevel=2, + ) + else: + self._timeout = timeout # type: ignore[assignment] + if read_timeout is not sentinel: + raise ValueError( + "read_timeout and timeout parameters " + "conflict, please setup " + "timeout.read" + ) + if conn_timeout is not None: + raise ValueError( + "conn_timeout and timeout parameters " + "conflict, please setup " + "timeout.connect" + ) + self._raise_for_status = raise_for_status + self._auto_decompress = auto_decompress + self._trust_env = trust_env + self._requote_redirect_url = requote_redirect_url + self._read_bufsize = read_bufsize + + # Convert to list of tuples + if headers: + real_headers: CIMultiDict[str] = CIMultiDict(headers) + else: + real_headers = CIMultiDict() + self._default_headers: CIMultiDict[str] = real_headers + if skip_auto_headers is not None: + self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers) + else: + self._skip_auto_headers = frozenset() + + self._request_class = request_class + self._response_class = response_class + self._ws_response_class = ws_response_class + + self._trace_configs = trace_configs or [] + for trace_config in self._trace_configs: + trace_config.freeze() + + def __init_subclass__(cls: Type["ClientSession"]) -> None: + warnings.warn( + "Inheritance class {} from ClientSession " + "is discouraged".format(cls.__name__), + DeprecationWarning, + stacklevel=2, + ) + + if DEBUG: + + def __setattr__(self, name: str, val: Any) -> None: + if name not in self.ATTRS: + warnings.warn( + "Setting custom ClientSession.{} attribute " + "is discouraged".format(name), + DeprecationWarning, + stacklevel=2, + ) + super().__setattr__(name, val) + + def __del__(self, _warnings: Any = warnings) -> None: + if not self.closed: + if PY_36: + kwargs = {"source": self} + else: + kwargs = {} + _warnings.warn( + f"Unclosed client session {self!r}", ResourceWarning, **kwargs + ) + context = {"client_session": self, "message": "Unclosed client session"} + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + def request( + self, method: str, url: StrOrURL, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP request.""" + return _RequestContextManager(self._request(method, url, **kwargs)) + + def _build_url(self, str_or_url: StrOrURL) -> URL: + url = URL(str_or_url) + if self._base_url is None: + return url + else: + assert not url.is_absolute() and url.path.startswith("/") + return self._base_url.join(url) + + async def _request( + self, + method: str, + str_or_url: StrOrURL, + *, + params: Optional[Mapping[str, str]] = None, + data: Any = None, + json: Any = None, + cookies: Optional[LooseCookies] = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + auth: Optional[BasicAuth] = None, + allow_redirects: bool = True, + max_redirects: int = 10, + compress: Optional[str] = None, + chunked: Optional[bool] = None, + expect100: bool = False, + raise_for_status: Optional[bool] = None, + read_until_eof: bool = True, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + timeout: Union[ClientTimeout, object] = sentinel, + verify_ssl: Optional[bool] = None, + fingerprint: Optional[bytes] = None, + ssl_context: Optional[SSLContext] = None, + ssl: Optional[Union[SSLContext, bool, Fingerprint]] = None, + proxy_headers: Optional[LooseHeaders] = None, + trace_request_ctx: Optional[SimpleNamespace] = None, + read_bufsize: Optional[int] = None, + ) -> ClientResponse: + + # NOTE: timeout clamps existing connect and read timeouts. We cannot + # set the default to None because we need to detect if the user wants + # to use the existing timeouts by setting timeout to None. + + if self.closed: + raise RuntimeError("Session is closed") + + ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint) + + if data is not None and json is not None: + raise ValueError( + "data and json parameters can not be used at the same time" + ) + elif json is not None: + data = payload.JsonPayload(json, dumps=self._json_serialize) + + if not isinstance(chunked, bool) and chunked is not None: + warnings.warn("Chunk size is deprecated #1615", DeprecationWarning) + + redirects = 0 + history = [] + version = self._version + + # Merge with default headers and transform to CIMultiDict + headers = self._prepare_headers(headers) + proxy_headers = self._prepare_headers(proxy_headers) + + try: + url = self._build_url(str_or_url) + except ValueError as e: + raise InvalidURL(str_or_url) from e + + skip_headers = set(self._skip_auto_headers) + if skip_auto_headers is not None: + for i in skip_auto_headers: + skip_headers.add(istr(i)) + + if proxy is not None: + try: + proxy = URL(proxy) + except ValueError as e: + raise InvalidURL(proxy) from e + + if timeout is sentinel: + real_timeout: ClientTimeout = self._timeout + else: + if not isinstance(timeout, ClientTimeout): + real_timeout = ClientTimeout(total=timeout) # type: ignore[arg-type] + else: + real_timeout = timeout + # timeout is cumulative for all request operations + # (request, redirects, responses, data consuming) + tm = TimeoutHandle(self._loop, real_timeout.total) + handle = tm.start() + + if read_bufsize is None: + read_bufsize = self._read_bufsize + + traces = [ + Trace( + self, + trace_config, + trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx), + ) + for trace_config in self._trace_configs + ] + + for trace in traces: + await trace.send_request_start(method, url.update_query(params), headers) + + timer = tm.timer() + try: + with timer: + while True: + url, auth_from_url = strip_auth_from_url(url) + if auth and auth_from_url: + raise ValueError( + "Cannot combine AUTH argument with " + "credentials encoded in URL" + ) + + if auth is None: + auth = auth_from_url + if auth is None: + auth = self._default_auth + # It would be confusing if we support explicit + # Authorization header with auth argument + if ( + headers is not None + and auth is not None + and hdrs.AUTHORIZATION in headers + ): + raise ValueError( + "Cannot combine AUTHORIZATION header " + "with AUTH argument or credentials " + "encoded in URL" + ) + + all_cookies = self._cookie_jar.filter_cookies(url) + + if cookies is not None: + tmp_cookie_jar = CookieJar() + tmp_cookie_jar.update_cookies(cookies) + req_cookies = tmp_cookie_jar.filter_cookies(url) + if req_cookies: + all_cookies.load(req_cookies) + + if proxy is not None: + proxy = URL(proxy) + elif self._trust_env: + with suppress(LookupError): + proxy, proxy_auth = get_env_proxy_for_url(url) + + req = self._request_class( + method, + url, + params=params, + headers=headers, + skip_auto_headers=skip_headers, + data=data, + cookies=all_cookies, + auth=auth, + version=version, + compress=compress, + chunked=chunked, + expect100=expect100, + loop=self._loop, + response_class=self._response_class, + proxy=proxy, + proxy_auth=proxy_auth, + timer=timer, + session=self, + ssl=ssl, + proxy_headers=proxy_headers, + traces=traces, + ) + + # connection timeout + try: + async with ceil_timeout(real_timeout.connect): + assert self._connector is not None + conn = await self._connector.connect( + req, traces=traces, timeout=real_timeout + ) + except asyncio.TimeoutError as exc: + raise ServerTimeoutError( + "Connection timeout " "to host {}".format(url) + ) from exc + + assert conn.transport is not None + + assert conn.protocol is not None + conn.protocol.set_response_params( + timer=timer, + skip_payload=method.upper() == "HEAD", + read_until_eof=read_until_eof, + auto_decompress=self._auto_decompress, + read_timeout=real_timeout.sock_read, + read_bufsize=read_bufsize, + ) + + try: + try: + resp = await req.send(conn) + try: + await resp.start(conn) + except BaseException: + resp.close() + raise + except BaseException: + conn.close() + raise + except ClientError: + raise + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise ClientOSError(*exc.args) from exc + + self._cookie_jar.update_cookies(resp.cookies, resp.url) + + # redirects + if resp.status in (301, 302, 303, 307, 308) and allow_redirects: + + for trace in traces: + await trace.send_request_redirect( + method, url.update_query(params), headers, resp + ) + + redirects += 1 + history.append(resp) + if max_redirects and redirects >= max_redirects: + resp.close() + raise TooManyRedirects( + history[0].request_info, tuple(history) + ) + + # For 301 and 302, mimic IE, now changed in RFC + # https://github.com/kennethreitz/requests/pull/269 + if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or ( + resp.status in (301, 302) and resp.method == hdrs.METH_POST + ): + method = hdrs.METH_GET + data = None + if headers.get(hdrs.CONTENT_LENGTH): + headers.pop(hdrs.CONTENT_LENGTH) + + r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get( + hdrs.URI + ) + if r_url is None: + # see github.com/aio-libs/aiohttp/issues/2022 + break + else: + # reading from correct redirection + # response is forbidden + resp.release() + + try: + parsed_url = URL( + r_url, encoded=not self._requote_redirect_url + ) + + except ValueError as e: + raise InvalidURL(r_url) from e + + scheme = parsed_url.scheme + if scheme not in ("http", "https", ""): + resp.close() + raise ValueError("Can redirect only to http or https") + elif not scheme: + parsed_url = url.join(parsed_url) + + if url.origin() != parsed_url.origin(): + auth = None + headers.pop(hdrs.AUTHORIZATION, None) + + url = parsed_url + params = None + resp.release() + continue + + break + + # check response status + if raise_for_status is None: + raise_for_status = self._raise_for_status + if raise_for_status: + resp.raise_for_status() + + # register connection + if handle is not None: + if resp.connection is not None: + resp.connection.add_callback(handle.cancel) + else: + handle.cancel() + + resp._history = tuple(history) + + for trace in traces: + await trace.send_request_end( + method, url.update_query(params), headers, resp + ) + return resp + + except BaseException as e: + # cleanup timer + tm.close() + if handle: + handle.cancel() + handle = None + + for trace in traces: + await trace.send_request_exception( + method, url.update_query(params), headers, e + ) + raise + + def ws_connect( + self, + url: StrOrURL, + *, + method: str = hdrs.METH_GET, + protocols: Iterable[str] = (), + timeout: float = 10.0, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + auth: Optional[BasicAuth] = None, + origin: Optional[str] = None, + params: Optional[Mapping[str, str]] = None, + headers: Optional[LooseHeaders] = None, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + ssl: Union[SSLContext, bool, None, Fingerprint] = None, + verify_ssl: Optional[bool] = None, + fingerprint: Optional[bytes] = None, + ssl_context: Optional[SSLContext] = None, + proxy_headers: Optional[LooseHeaders] = None, + compress: int = 0, + max_msg_size: int = 4 * 1024 * 1024, + ) -> "_WSRequestContextManager": + """Initiate websocket connection.""" + return _WSRequestContextManager( + self._ws_connect( + url, + method=method, + protocols=protocols, + timeout=timeout, + receive_timeout=receive_timeout, + autoclose=autoclose, + autoping=autoping, + heartbeat=heartbeat, + auth=auth, + origin=origin, + params=params, + headers=headers, + proxy=proxy, + proxy_auth=proxy_auth, + ssl=ssl, + verify_ssl=verify_ssl, + fingerprint=fingerprint, + ssl_context=ssl_context, + proxy_headers=proxy_headers, + compress=compress, + max_msg_size=max_msg_size, + ) + ) + + async def _ws_connect( + self, + url: StrOrURL, + *, + method: str = hdrs.METH_GET, + protocols: Iterable[str] = (), + timeout: float = 10.0, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + auth: Optional[BasicAuth] = None, + origin: Optional[str] = None, + params: Optional[Mapping[str, str]] = None, + headers: Optional[LooseHeaders] = None, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + ssl: Union[SSLContext, bool, None, Fingerprint] = None, + verify_ssl: Optional[bool] = None, + fingerprint: Optional[bytes] = None, + ssl_context: Optional[SSLContext] = None, + proxy_headers: Optional[LooseHeaders] = None, + compress: int = 0, + max_msg_size: int = 4 * 1024 * 1024, + ) -> ClientWebSocketResponse: + + if headers is None: + real_headers: CIMultiDict[str] = CIMultiDict() + else: + real_headers = CIMultiDict(headers) + + default_headers = { + hdrs.UPGRADE: "websocket", + hdrs.CONNECTION: "upgrade", + hdrs.SEC_WEBSOCKET_VERSION: "13", + } + + for key, value in default_headers.items(): + real_headers.setdefault(key, value) + + sec_key = base64.b64encode(os.urandom(16)) + real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode() + + if protocols: + real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols) + if origin is not None: + real_headers[hdrs.ORIGIN] = origin + if compress: + extstr = ws_ext_gen(compress=compress) + real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr + + ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint) + + # send request + resp = await self.request( + method, + url, + params=params, + headers=real_headers, + read_until_eof=False, + auth=auth, + proxy=proxy, + proxy_auth=proxy_auth, + ssl=ssl, + proxy_headers=proxy_headers, + ) + + try: + # check handshake + if resp.status != 101: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid response status", + status=resp.status, + headers=resp.headers, + ) + + if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket": + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid upgrade header", + status=resp.status, + headers=resp.headers, + ) + + if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade": + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid connection header", + status=resp.status, + headers=resp.headers, + ) + + # key calculation + r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "") + match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode() + if r_key != match: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid challenge response", + status=resp.status, + headers=resp.headers, + ) + + # websocket protocol + protocol = None + if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers: + resp_protocols = [ + proto.strip() + for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") + ] + + for proto in resp_protocols: + if proto in protocols: + protocol = proto + break + + # websocket compress + notakeover = False + if compress: + compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS) + if compress_hdrs: + try: + compress, notakeover = ws_ext_parse(compress_hdrs) + except WSHandshakeError as exc: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message=exc.args[0], + status=resp.status, + headers=resp.headers, + ) from exc + else: + compress = 0 + notakeover = False + + conn = resp.connection + assert conn is not None + conn_proto = conn.protocol + assert conn_proto is not None + transport = conn.transport + assert transport is not None + reader: FlowControlDataQueue[WSMessage] = FlowControlDataQueue( + conn_proto, 2**16, loop=self._loop + ) + conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader) + writer = WebSocketWriter( + conn_proto, + transport, + use_mask=True, + compress=compress, + notakeover=notakeover, + ) + except BaseException: + resp.close() + raise + else: + return self._ws_response_class( + reader, + writer, + protocol, + resp, + timeout, + autoclose, + autoping, + self._loop, + receive_timeout=receive_timeout, + heartbeat=heartbeat, + compress=compress, + client_notakeover=notakeover, + ) + + def _prepare_headers(self, headers: Optional[LooseHeaders]) -> "CIMultiDict[str]": + """Add default headers and transform it to CIMultiDict""" + # Convert headers to MultiDict + result = CIMultiDict(self._default_headers) + if headers: + if not isinstance(headers, (MultiDictProxy, MultiDict)): + headers = CIMultiDict(headers) + added_names: Set[str] = set() + for key, value in headers.items(): + if key in added_names: + result.add(key, value) + else: + result[key] = value + added_names.add(key) + return result + + def get( + self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP GET request.""" + return _RequestContextManager( + self._request(hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs) + ) + + def options( + self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP OPTIONS request.""" + return _RequestContextManager( + self._request( + hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs + ) + ) + + def head( + self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP HEAD request.""" + return _RequestContextManager( + self._request( + hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs + ) + ) + + def post( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP POST request.""" + return _RequestContextManager( + self._request(hdrs.METH_POST, url, data=data, **kwargs) + ) + + def put( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP PUT request.""" + return _RequestContextManager( + self._request(hdrs.METH_PUT, url, data=data, **kwargs) + ) + + def patch( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP PATCH request.""" + return _RequestContextManager( + self._request(hdrs.METH_PATCH, url, data=data, **kwargs) + ) + + def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager": + """Perform HTTP DELETE request.""" + return _RequestContextManager(self._request(hdrs.METH_DELETE, url, **kwargs)) + + async def close(self) -> None: + """Close underlying connector. + + Release all acquired resources. + """ + if not self.closed: + if self._connector is not None and self._connector_owner: + await self._connector.close() + self._connector = None + + @property + def closed(self) -> bool: + """Is client session closed. + + A readonly property. + """ + return self._connector is None or self._connector.closed + + @property + def connector(self) -> Optional[BaseConnector]: + """Connector instance used for the session.""" + return self._connector + + @property + def cookie_jar(self) -> AbstractCookieJar: + """The session cookies.""" + return self._cookie_jar + + @property + def version(self) -> Tuple[int, int]: + """The session HTTP protocol version.""" + return self._version + + @property + def requote_redirect_url(self) -> bool: + """Do URL requoting on redirection handling.""" + return self._requote_redirect_url + + @requote_redirect_url.setter + def requote_redirect_url(self, val: bool) -> None: + """Do URL requoting on redirection handling.""" + warnings.warn( + "session.requote_redirect_url modification " "is deprecated #2778", + DeprecationWarning, + stacklevel=2, + ) + self._requote_redirect_url = val + + @property + def loop(self) -> asyncio.AbstractEventLoop: + """Session's loop.""" + warnings.warn( + "client.loop property is deprecated", DeprecationWarning, stacklevel=2 + ) + return self._loop + + @property + def timeout(self) -> ClientTimeout: + """Timeout for the session.""" + return self._timeout + + @property + def headers(self) -> "CIMultiDict[str]": + """The default headers of the client session.""" + return self._default_headers + + @property + def skip_auto_headers(self) -> FrozenSet[istr]: + """Headers for which autogeneration should be skipped""" + return self._skip_auto_headers + + @property + def auth(self) -> Optional[BasicAuth]: + """An object that represents HTTP Basic Authorization""" + return self._default_auth + + @property + def json_serialize(self) -> JSONEncoder: + """Json serializer callable""" + return self._json_serialize + + @property + def connector_owner(self) -> bool: + """Should connector be closed on session closing""" + return self._connector_owner + + @property + def raise_for_status( + self, + ) -> Union[bool, Callable[[ClientResponse], Awaitable[None]]]: + """Should `ClientResponse.raise_for_status()` be called for each response.""" + return self._raise_for_status + + @property + def auto_decompress(self) -> bool: + """Should the body response be automatically decompressed.""" + return self._auto_decompress + + @property + def trust_env(self) -> bool: + """ + Should proxies information from environment or netrc be trusted. + + Information is from HTTP_PROXY / HTTPS_PROXY environment variables + or ~/.netrc file if present. + """ + return self._trust_env + + @property + def trace_configs(self) -> List[TraceConfig]: + """A list of TraceConfig instances used for client tracing""" + return self._trace_configs + + def detach(self) -> None: + """Detach connector from session without closing the former. + + Session is switched to closed state anyway. + """ + self._connector = None + + def __enter__(self) -> None: + raise TypeError("Use async with instead") + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + # __exit__ should exist in pair with __enter__ but never executed + pass # pragma: no cover + + async def __aenter__(self) -> "ClientSession": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + await self.close() + + +class _BaseRequestContextManager(Coroutine[Any, Any, _RetType], Generic[_RetType]): + + __slots__ = ("_coro", "_resp") + + def __init__(self, coro: Coroutine["asyncio.Future[Any]", None, _RetType]) -> None: + self._coro = coro + + def send(self, arg: None) -> "asyncio.Future[Any]": + return self._coro.send(arg) + + def throw(self, arg: BaseException) -> None: # type: ignore[arg-type,override] + self._coro.throw(arg) + + def close(self) -> None: + return self._coro.close() + + def __await__(self) -> Generator[Any, None, _RetType]: + ret = self._coro.__await__() + return ret + + def __iter__(self) -> Generator[Any, None, _RetType]: + return self.__await__() + + async def __aenter__(self) -> _RetType: + self._resp = await self._coro + return self._resp + + +class _RequestContextManager(_BaseRequestContextManager[ClientResponse]): + __slots__ = () + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + # We're basing behavior on the exception as it can be caused by + # user code unrelated to the status of the connection. If you + # would like to close a connection you must do that + # explicitly. Otherwise connection error handling should kick in + # and close/recycle the connection as required. + self._resp.release() + + +class _WSRequestContextManager(_BaseRequestContextManager[ClientWebSocketResponse]): + __slots__ = () + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + await self._resp.close() + + +class _SessionRequestContextManager: + + __slots__ = ("_coro", "_resp", "_session") + + def __init__( + self, + coro: Coroutine["asyncio.Future[Any]", None, ClientResponse], + session: ClientSession, + ) -> None: + self._coro = coro + self._resp: Optional[ClientResponse] = None + self._session = session + + async def __aenter__(self) -> ClientResponse: + try: + self._resp = await self._coro + except BaseException: + await self._session.close() + raise + else: + return self._resp + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + assert self._resp is not None + self._resp.close() + await self._session.close() + + +def request( + method: str, + url: StrOrURL, + *, + params: Optional[Mapping[str, str]] = None, + data: Any = None, + json: Any = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + auth: Optional[BasicAuth] = None, + allow_redirects: bool = True, + max_redirects: int = 10, + compress: Optional[str] = None, + chunked: Optional[bool] = None, + expect100: bool = False, + raise_for_status: Optional[bool] = None, + read_until_eof: bool = True, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + timeout: Union[ClientTimeout, object] = sentinel, + cookies: Optional[LooseCookies] = None, + version: HttpVersion = http.HttpVersion11, + connector: Optional[BaseConnector] = None, + read_bufsize: Optional[int] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> _SessionRequestContextManager: + """Constructs and sends a request. + + Returns response object. + method - HTTP method + url - request url + params - (optional) Dictionary or bytes to be sent in the query + string of the new request + data - (optional) Dictionary, bytes, or file-like object to + send in the body of the request + json - (optional) Any json compatible python object + headers - (optional) Dictionary of HTTP Headers to send with + the request + cookies - (optional) Dict object to send with the request + auth - (optional) BasicAuth named tuple represent HTTP Basic Auth + auth - aiohttp.helpers.BasicAuth + allow_redirects - (optional) If set to False, do not follow + redirects + version - Request HTTP version. + compress - Set to True if request has to be compressed + with deflate encoding. + chunked - Set to chunk size for chunked transfer encoding. + expect100 - Expect 100-continue response from server. + connector - BaseConnector sub-class instance to support + connection pooling. + read_until_eof - Read response until eof if response + does not have Content-Length header. + loop - Optional event loop. + timeout - Optional ClientTimeout settings structure, 5min + total timeout by default. + Usage:: + >>> import aiohttp + >>> resp = await aiohttp.request('GET', 'http://python.org/') + >>> resp + + >>> data = await resp.read() + """ + connector_owner = False + if connector is None: + connector_owner = True + connector = TCPConnector(loop=loop, force_close=True) + + session = ClientSession( + loop=loop, + cookies=cookies, + version=version, + timeout=timeout, + connector=connector, + connector_owner=connector_owner, + ) + + return _SessionRequestContextManager( + session._request( + method, + url, + params=params, + data=data, + json=json, + headers=headers, + skip_auto_headers=skip_auto_headers, + auth=auth, + allow_redirects=allow_redirects, + max_redirects=max_redirects, + compress=compress, + chunked=chunked, + expect100=expect100, + raise_for_status=raise_for_status, + read_until_eof=read_until_eof, + proxy=proxy, + proxy_auth=proxy_auth, + read_bufsize=read_bufsize, + ), + session, + ) diff --git a/lib/aiohttp/client_exceptions.py b/lib/aiohttp/client_exceptions.py new file mode 100644 index 0000000..c640e1e --- /dev/null +++ b/lib/aiohttp/client_exceptions.py @@ -0,0 +1,342 @@ +"""HTTP related errors.""" + +import asyncio +import warnings +from typing import TYPE_CHECKING, Any, Optional, Tuple, Union + +from .http_parser import RawResponseMessage +from .typedefs import LooseHeaders + +try: + import ssl + + SSLContext = ssl.SSLContext +except ImportError: # pragma: no cover + ssl = SSLContext = None # type: ignore[assignment] + + +if TYPE_CHECKING: # pragma: no cover + from .client_reqrep import ClientResponse, ConnectionKey, Fingerprint, RequestInfo +else: + RequestInfo = ClientResponse = ConnectionKey = None + +__all__ = ( + "ClientError", + "ClientConnectionError", + "ClientOSError", + "ClientConnectorError", + "ClientProxyConnectionError", + "ClientSSLError", + "ClientConnectorSSLError", + "ClientConnectorCertificateError", + "ServerConnectionError", + "ServerTimeoutError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ClientResponseError", + "ClientHttpProxyError", + "WSServerHandshakeError", + "ContentTypeError", + "ClientPayloadError", + "InvalidURL", +) + + +class ClientError(Exception): + """Base class for client connection errors.""" + + +class ClientResponseError(ClientError): + """Connection error during reading response. + + request_info: instance of RequestInfo + """ + + def __init__( + self, + request_info: RequestInfo, + history: Tuple[ClientResponse, ...], + *, + code: Optional[int] = None, + status: Optional[int] = None, + message: str = "", + headers: Optional[LooseHeaders] = None, + ) -> None: + self.request_info = request_info + if code is not None: + if status is not None: + raise ValueError( + "Both code and status arguments are provided; " + "code is deprecated, use status instead" + ) + warnings.warn( + "code argument is deprecated, use status instead", + DeprecationWarning, + stacklevel=2, + ) + if status is not None: + self.status = status + elif code is not None: + self.status = code + else: + self.status = 0 + self.message = message + self.headers = headers + self.history = history + self.args = (request_info, history) + + def __str__(self) -> str: + return "{}, message={!r}, url={!r}".format( + self.status, + self.message, + self.request_info.real_url, + ) + + def __repr__(self) -> str: + args = f"{self.request_info!r}, {self.history!r}" + if self.status != 0: + args += f", status={self.status!r}" + if self.message != "": + args += f", message={self.message!r}" + if self.headers is not None: + args += f", headers={self.headers!r}" + return f"{type(self).__name__}({args})" + + @property + def code(self) -> int: + warnings.warn( + "code property is deprecated, use status instead", + DeprecationWarning, + stacklevel=2, + ) + return self.status + + @code.setter + def code(self, value: int) -> None: + warnings.warn( + "code property is deprecated, use status instead", + DeprecationWarning, + stacklevel=2, + ) + self.status = value + + +class ContentTypeError(ClientResponseError): + """ContentType found is not valid.""" + + +class WSServerHandshakeError(ClientResponseError): + """websocket server handshake error.""" + + +class ClientHttpProxyError(ClientResponseError): + """HTTP proxy error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + proxy responds with status other than ``200 OK`` + on ``CONNECT`` request. + """ + + +class TooManyRedirects(ClientResponseError): + """Client was redirected too many times.""" + + +class ClientConnectionError(ClientError): + """Base class for client socket errors.""" + + +class ClientOSError(ClientConnectionError, OSError): + """OSError error.""" + + +class ClientConnectorError(ClientOSError): + """Client connector error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + a connection can not be established. + """ + + def __init__(self, connection_key: ConnectionKey, os_error: OSError) -> None: + self._conn_key = connection_key + self._os_error = os_error + super().__init__(os_error.errno, os_error.strerror) + self.args = (connection_key, os_error) + + @property + def os_error(self) -> OSError: + return self._os_error + + @property + def host(self) -> str: + return self._conn_key.host + + @property + def port(self) -> Optional[int]: + return self._conn_key.port + + @property + def ssl(self) -> Union[SSLContext, None, bool, "Fingerprint"]: + return self._conn_key.ssl + + def __str__(self) -> str: + return "Cannot connect to host {0.host}:{0.port} ssl:{1} [{2}]".format( + self, self.ssl if self.ssl is not None else "default", self.strerror + ) + + # OSError.__reduce__ does too much black magick + __reduce__ = BaseException.__reduce__ + + +class ClientProxyConnectionError(ClientConnectorError): + """Proxy connection error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + connection to proxy can not be established. + """ + + +class UnixClientConnectorError(ClientConnectorError): + """Unix connector error. + + Raised in :py:class:`aiohttp.connector.UnixConnector` + if connection to unix socket can not be established. + """ + + def __init__( + self, path: str, connection_key: ConnectionKey, os_error: OSError + ) -> None: + self._path = path + super().__init__(connection_key, os_error) + + @property + def path(self) -> str: + return self._path + + def __str__(self) -> str: + return "Cannot connect to unix socket {0.path} ssl:{1} [{2}]".format( + self, self.ssl if self.ssl is not None else "default", self.strerror + ) + + +class ServerConnectionError(ClientConnectionError): + """Server connection errors.""" + + +class ServerDisconnectedError(ServerConnectionError): + """Server disconnected.""" + + def __init__(self, message: Union[RawResponseMessage, str, None] = None) -> None: + if message is None: + message = "Server disconnected" + + self.args = (message,) + self.message = message + + +class ServerTimeoutError(ServerConnectionError, asyncio.TimeoutError): + """Server timeout error.""" + + +class ServerFingerprintMismatch(ServerConnectionError): + """SSL certificate does not match expected fingerprint.""" + + def __init__(self, expected: bytes, got: bytes, host: str, port: int) -> None: + self.expected = expected + self.got = got + self.host = host + self.port = port + self.args = (expected, got, host, port) + + def __repr__(self) -> str: + return "<{} expected={!r} got={!r} host={!r} port={!r}>".format( + self.__class__.__name__, self.expected, self.got, self.host, self.port + ) + + +class ClientPayloadError(ClientError): + """Response payload error.""" + + +class InvalidURL(ClientError, ValueError): + """Invalid URL. + + URL used for fetching is malformed, e.g. it doesn't contains host + part. + """ + + # Derive from ValueError for backward compatibility + + def __init__(self, url: Any) -> None: + # The type of url is not yarl.URL because the exception can be raised + # on URL(url) call + super().__init__(url) + + @property + def url(self) -> Any: + return self.args[0] + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.url}>" + + +class ClientSSLError(ClientConnectorError): + """Base error for ssl.*Errors.""" + + +if ssl is not None: + cert_errors = (ssl.CertificateError,) + cert_errors_bases = ( + ClientSSLError, + ssl.CertificateError, + ) + + ssl_errors = (ssl.SSLError,) + ssl_error_bases = (ClientSSLError, ssl.SSLError) +else: # pragma: no cover + cert_errors = tuple() + cert_errors_bases = ( + ClientSSLError, + ValueError, + ) + + ssl_errors = tuple() + ssl_error_bases = (ClientSSLError,) + + +class ClientConnectorSSLError(*ssl_error_bases): # type: ignore[misc] + """Response ssl error.""" + + +class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore[misc] + """Response certificate error.""" + + def __init__( + self, connection_key: ConnectionKey, certificate_error: Exception + ) -> None: + self._conn_key = connection_key + self._certificate_error = certificate_error + self.args = (connection_key, certificate_error) + + @property + def certificate_error(self) -> Exception: + return self._certificate_error + + @property + def host(self) -> str: + return self._conn_key.host + + @property + def port(self) -> Optional[int]: + return self._conn_key.port + + @property + def ssl(self) -> bool: + return self._conn_key.is_ssl + + def __str__(self) -> str: + return ( + "Cannot connect to host {0.host}:{0.port} ssl:{0.ssl} " + "[{0.certificate_error.__class__.__name__}: " + "{0.certificate_error.args}]".format(self) + ) diff --git a/lib/aiohttp/client_proto.py b/lib/aiohttp/client_proto.py new file mode 100644 index 0000000..3041157 --- /dev/null +++ b/lib/aiohttp/client_proto.py @@ -0,0 +1,251 @@ +import asyncio +from contextlib import suppress +from typing import Any, Optional, Tuple + +from .base_protocol import BaseProtocol +from .client_exceptions import ( + ClientOSError, + ClientPayloadError, + ServerDisconnectedError, + ServerTimeoutError, +) +from .helpers import BaseTimerContext +from .http import HttpResponseParser, RawResponseMessage +from .streams import EMPTY_PAYLOAD, DataQueue, StreamReader + + +class ResponseHandler(BaseProtocol, DataQueue[Tuple[RawResponseMessage, StreamReader]]): + """Helper class to adapt between Protocol and StreamReader.""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + BaseProtocol.__init__(self, loop=loop) + DataQueue.__init__(self, loop) + + self._should_close = False + + self._payload: Optional[StreamReader] = None + self._skip_payload = False + self._payload_parser = None + + self._timer = None + + self._tail = b"" + self._upgraded = False + self._parser: Optional[HttpResponseParser] = None + + self._read_timeout: Optional[float] = None + self._read_timeout_handle: Optional[asyncio.TimerHandle] = None + + @property + def upgraded(self) -> bool: + return self._upgraded + + @property + def should_close(self) -> bool: + if self._payload is not None and not self._payload.is_eof() or self._upgraded: + return True + + return ( + self._should_close + or self._upgraded + or self.exception() is not None + or self._payload_parser is not None + or len(self) > 0 + or bool(self._tail) + ) + + def force_close(self) -> None: + self._should_close = True + + def close(self) -> None: + transport = self.transport + if transport is not None: + transport.close() + self.transport = None + self._payload = None + self._drop_timeout() + + def is_connected(self) -> bool: + return self.transport is not None and not self.transport.is_closing() + + def connection_lost(self, exc: Optional[BaseException]) -> None: + self._drop_timeout() + + if self._payload_parser is not None: + with suppress(Exception): + self._payload_parser.feed_eof() + + uncompleted = None + if self._parser is not None: + try: + uncompleted = self._parser.feed_eof() + except Exception: + if self._payload is not None: + self._payload.set_exception( + ClientPayloadError("Response payload is not completed") + ) + + if not self.is_eof(): + if isinstance(exc, OSError): + exc = ClientOSError(*exc.args) + if exc is None: + exc = ServerDisconnectedError(uncompleted) + # assigns self._should_close to True as side effect, + # we do it anyway below + self.set_exception(exc) + + self._should_close = True + self._parser = None + self._payload = None + self._payload_parser = None + self._reading_paused = False + + super().connection_lost(exc) + + def eof_received(self) -> None: + # should call parser.feed_eof() most likely + self._drop_timeout() + + def pause_reading(self) -> None: + super().pause_reading() + self._drop_timeout() + + def resume_reading(self) -> None: + super().resume_reading() + self._reschedule_timeout() + + def set_exception(self, exc: BaseException) -> None: + self._should_close = True + self._drop_timeout() + super().set_exception(exc) + + def set_parser(self, parser: Any, payload: Any) -> None: + # TODO: actual types are: + # parser: WebSocketReader + # payload: FlowControlDataQueue + # but they are not generi enough + # Need an ABC for both types + self._payload = payload + self._payload_parser = parser + + self._drop_timeout() + + if self._tail: + data, self._tail = self._tail, b"" + self.data_received(data) + + def set_response_params( + self, + *, + timer: Optional[BaseTimerContext] = None, + skip_payload: bool = False, + read_until_eof: bool = False, + auto_decompress: bool = True, + read_timeout: Optional[float] = None, + read_bufsize: int = 2**16, + ) -> None: + self._skip_payload = skip_payload + + self._read_timeout = read_timeout + self._reschedule_timeout() + + self._parser = HttpResponseParser( + self, + self._loop, + read_bufsize, + timer=timer, + payload_exception=ClientPayloadError, + response_with_body=not skip_payload, + read_until_eof=read_until_eof, + auto_decompress=auto_decompress, + ) + + if self._tail: + data, self._tail = self._tail, b"" + self.data_received(data) + + def _drop_timeout(self) -> None: + if self._read_timeout_handle is not None: + self._read_timeout_handle.cancel() + self._read_timeout_handle = None + + def _reschedule_timeout(self) -> None: + timeout = self._read_timeout + if self._read_timeout_handle is not None: + self._read_timeout_handle.cancel() + + if timeout: + self._read_timeout_handle = self._loop.call_later( + timeout, self._on_read_timeout + ) + else: + self._read_timeout_handle = None + + def _on_read_timeout(self) -> None: + exc = ServerTimeoutError("Timeout on reading data from socket") + self.set_exception(exc) + if self._payload is not None: + self._payload.set_exception(exc) + + def data_received(self, data: bytes) -> None: + self._reschedule_timeout() + + if not data: + return + + # custom payload parser + if self._payload_parser is not None: + eof, tail = self._payload_parser.feed_data(data) + if eof: + self._payload = None + self._payload_parser = None + + if tail: + self.data_received(tail) + return + else: + if self._upgraded or self._parser is None: + # i.e. websocket connection, websocket parser is not set yet + self._tail += data + else: + # parse http messages + try: + messages, upgraded, tail = self._parser.feed_data(data) + except BaseException as exc: + if self.transport is not None: + # connection.release() could be called BEFORE + # data_received(), the transport is already + # closed in this case + self.transport.close() + # should_close is True after the call + self.set_exception(exc) + return + + self._upgraded = upgraded + + payload: Optional[StreamReader] = None + for message, payload in messages: + if message.should_close: + self._should_close = True + + self._payload = payload + + if self._skip_payload or message.code in (204, 304): + self.feed_data((message, EMPTY_PAYLOAD), 0) + else: + self.feed_data((message, payload), 0) + if payload is not None: + # new message(s) was processed + # register timeout handler unsubscribing + # either on end-of-stream or immediately for + # EMPTY_PAYLOAD + if payload is not EMPTY_PAYLOAD: + payload.on_eof(self._drop_timeout) + else: + self._drop_timeout() + + if tail: + if upgraded: + self.data_received(tail) + else: + self._tail = tail diff --git a/lib/aiohttp/client_reqrep.py b/lib/aiohttp/client_reqrep.py new file mode 100644 index 0000000..28b8a28 --- /dev/null +++ b/lib/aiohttp/client_reqrep.py @@ -0,0 +1,1134 @@ +import asyncio +import codecs +import functools +import io +import re +import sys +import traceback +import warnings +from hashlib import md5, sha1, sha256 +from http.cookies import CookieError, Morsel, SimpleCookie +from types import MappingProxyType, TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Iterable, + List, + Mapping, + Optional, + Tuple, + Type, + Union, + cast, +) + +import attr +from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy +from yarl import URL + +from . import hdrs, helpers, http, multipart, payload +from .abc import AbstractStreamWriter +from .client_exceptions import ( + ClientConnectionError, + ClientOSError, + ClientResponseError, + ContentTypeError, + InvalidURL, + ServerFingerprintMismatch, +) +from .formdata import FormData +from .helpers import ( + PY_36, + BaseTimerContext, + BasicAuth, + HeadersMixin, + TimerNoop, + noop, + reify, + set_result, +) +from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11, StreamWriter +from .log import client_logger +from .streams import StreamReader +from .typedefs import ( + DEFAULT_JSON_DECODER, + JSONDecoder, + LooseCookies, + LooseHeaders, + RawHeaders, +) + +try: + import ssl + from ssl import SSLContext +except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + +try: + import cchardet as chardet +except ImportError: # pragma: no cover + import charset_normalizer as chardet # type: ignore[no-redef] + + +__all__ = ("ClientRequest", "ClientResponse", "RequestInfo", "Fingerprint") + + +if TYPE_CHECKING: # pragma: no cover + from .client import ClientSession + from .connector import Connection + from .tracing import Trace + + +json_re = re.compile(r"^application/(?:[\w.+-]+?\+)?json") + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class ContentDisposition: + type: Optional[str] + parameters: "MappingProxyType[str, str]" + filename: Optional[str] + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class RequestInfo: + url: URL + method: str + headers: "CIMultiDictProxy[str]" + real_url: URL = attr.ib() + + @real_url.default + def real_url_default(self) -> URL: + return self.url + + +class Fingerprint: + HASHFUNC_BY_DIGESTLEN = { + 16: md5, + 20: sha1, + 32: sha256, + } + + def __init__(self, fingerprint: bytes) -> None: + digestlen = len(fingerprint) + hashfunc = self.HASHFUNC_BY_DIGESTLEN.get(digestlen) + if not hashfunc: + raise ValueError("fingerprint has invalid length") + elif hashfunc is md5 or hashfunc is sha1: + raise ValueError( + "md5 and sha1 are insecure and " "not supported. Use sha256." + ) + self._hashfunc = hashfunc + self._fingerprint = fingerprint + + @property + def fingerprint(self) -> bytes: + return self._fingerprint + + def check(self, transport: asyncio.Transport) -> None: + if not transport.get_extra_info("sslcontext"): + return + sslobj = transport.get_extra_info("ssl_object") + cert = sslobj.getpeercert(binary_form=True) + got = self._hashfunc(cert).digest() + if got != self._fingerprint: + host, port, *_ = transport.get_extra_info("peername") + raise ServerFingerprintMismatch(self._fingerprint, got, host, port) + + +if ssl is not None: + SSL_ALLOWED_TYPES = (ssl.SSLContext, bool, Fingerprint, type(None)) +else: # pragma: no cover + SSL_ALLOWED_TYPES = type(None) + + +def _merge_ssl_params( + ssl: Union["SSLContext", bool, Fingerprint, None], + verify_ssl: Optional[bool], + ssl_context: Optional["SSLContext"], + fingerprint: Optional[bytes], +) -> Union["SSLContext", bool, Fingerprint, None]: + if verify_ssl is not None and not verify_ssl: + warnings.warn( + "verify_ssl is deprecated, use ssl=False instead", + DeprecationWarning, + stacklevel=3, + ) + if ssl is not None: + raise ValueError( + "verify_ssl, ssl_context, fingerprint and ssl " + "parameters are mutually exclusive" + ) + else: + ssl = False + if ssl_context is not None: + warnings.warn( + "ssl_context is deprecated, use ssl=context instead", + DeprecationWarning, + stacklevel=3, + ) + if ssl is not None: + raise ValueError( + "verify_ssl, ssl_context, fingerprint and ssl " + "parameters are mutually exclusive" + ) + else: + ssl = ssl_context + if fingerprint is not None: + warnings.warn( + "fingerprint is deprecated, " "use ssl=Fingerprint(fingerprint) instead", + DeprecationWarning, + stacklevel=3, + ) + if ssl is not None: + raise ValueError( + "verify_ssl, ssl_context, fingerprint and ssl " + "parameters are mutually exclusive" + ) + else: + ssl = Fingerprint(fingerprint) + if not isinstance(ssl, SSL_ALLOWED_TYPES): + raise TypeError( + "ssl should be SSLContext, bool, Fingerprint or None, " + "got {!r} instead.".format(ssl) + ) + return ssl + + +@attr.s(auto_attribs=True, slots=True, frozen=True) +class ConnectionKey: + # the key should contain an information about used proxy / TLS + # to prevent reusing wrong connections from a pool + host: str + port: Optional[int] + is_ssl: bool + ssl: Union[SSLContext, None, bool, Fingerprint] + proxy: Optional[URL] + proxy_auth: Optional[BasicAuth] + proxy_headers_hash: Optional[int] # hash(CIMultiDict) + + +def _is_expected_content_type( + response_content_type: str, expected_content_type: str +) -> bool: + if expected_content_type == "application/json": + return json_re.match(response_content_type) is not None + return expected_content_type in response_content_type + + +class ClientRequest: + GET_METHODS = { + hdrs.METH_GET, + hdrs.METH_HEAD, + hdrs.METH_OPTIONS, + hdrs.METH_TRACE, + } + POST_METHODS = {hdrs.METH_PATCH, hdrs.METH_POST, hdrs.METH_PUT} + ALL_METHODS = GET_METHODS.union(POST_METHODS).union({hdrs.METH_DELETE}) + + DEFAULT_HEADERS = { + hdrs.ACCEPT: "*/*", + hdrs.ACCEPT_ENCODING: "gzip, deflate", + } + + body = b"" + auth = None + response = None + + _writer = None # async task for streaming data + _continue = None # waiter future for '100 Continue' response + + # N.B. + # Adding __del__ method with self._writer closing doesn't make sense + # because _writer is instance method, thus it keeps a reference to self. + # Until writer has finished finalizer will not be called. + + def __init__( + self, + method: str, + url: URL, + *, + params: Optional[Mapping[str, str]] = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Iterable[str] = frozenset(), + data: Any = None, + cookies: Optional[LooseCookies] = None, + auth: Optional[BasicAuth] = None, + version: http.HttpVersion = http.HttpVersion11, + compress: Optional[str] = None, + chunked: Optional[bool] = None, + expect100: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + response_class: Optional[Type["ClientResponse"]] = None, + proxy: Optional[URL] = None, + proxy_auth: Optional[BasicAuth] = None, + timer: Optional[BaseTimerContext] = None, + session: Optional["ClientSession"] = None, + ssl: Union[SSLContext, bool, Fingerprint, None] = None, + proxy_headers: Optional[LooseHeaders] = None, + traces: Optional[List["Trace"]] = None, + ): + + if loop is None: + loop = asyncio.get_event_loop() + + assert isinstance(url, URL), url + assert isinstance(proxy, (URL, type(None))), proxy + # FIXME: session is None in tests only, need to fix tests + # assert session is not None + self._session = cast("ClientSession", session) + if params: + q = MultiDict(url.query) + url2 = url.with_query(params) + q.extend(url2.query) + url = url.with_query(q) + self.original_url = url + self.url = url.with_fragment(None) + self.method = method.upper() + self.chunked = chunked + self.compress = compress + self.loop = loop + self.length = None + if response_class is None: + real_response_class = ClientResponse + else: + real_response_class = response_class + self.response_class: Type[ClientResponse] = real_response_class + self._timer = timer if timer is not None else TimerNoop() + self._ssl = ssl + + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + self.update_version(version) + self.update_host(url) + self.update_headers(headers) + self.update_auto_headers(skip_auto_headers) + self.update_cookies(cookies) + self.update_content_encoding(data) + self.update_auth(auth) + self.update_proxy(proxy, proxy_auth, proxy_headers) + + self.update_body_from_data(data) + if data is not None or self.method not in self.GET_METHODS: + self.update_transfer_encoding() + self.update_expect_continue(expect100) + if traces is None: + traces = [] + self._traces = traces + + def is_ssl(self) -> bool: + return self.url.scheme in ("https", "wss") + + @property + def ssl(self) -> Union["SSLContext", None, bool, Fingerprint]: + return self._ssl + + @property + def connection_key(self) -> ConnectionKey: + proxy_headers = self.proxy_headers + if proxy_headers: + h: Optional[int] = hash(tuple((k, v) for k, v in proxy_headers.items())) + else: + h = None + return ConnectionKey( + self.host, + self.port, + self.is_ssl(), + self.ssl, + self.proxy, + self.proxy_auth, + h, + ) + + @property + def host(self) -> str: + ret = self.url.raw_host + assert ret is not None + return ret + + @property + def port(self) -> Optional[int]: + return self.url.port + + @property + def request_info(self) -> RequestInfo: + headers: CIMultiDictProxy[str] = CIMultiDictProxy(self.headers) + return RequestInfo(self.url, self.method, headers, self.original_url) + + def update_host(self, url: URL) -> None: + """Update destination host, port and connection type (ssl).""" + # get host/port + if not url.raw_host: + raise InvalidURL(url) + + # basic auth info + username, password = url.user, url.password + if username: + self.auth = helpers.BasicAuth(username, password or "") + + def update_version(self, version: Union[http.HttpVersion, str]) -> None: + """Convert request version to two elements tuple. + + parser HTTP version '1.1' => (1, 1) + """ + if isinstance(version, str): + v = [part.strip() for part in version.split(".", 1)] + try: + version = http.HttpVersion(int(v[0]), int(v[1])) + except ValueError: + raise ValueError( + f"Can not parse http version number: {version}" + ) from None + self.version = version + + def update_headers(self, headers: Optional[LooseHeaders]) -> None: + """Update request headers.""" + self.headers: CIMultiDict[str] = CIMultiDict() + + # add host + netloc = cast(str, self.url.raw_host) + if helpers.is_ipv6_address(netloc): + netloc = f"[{netloc}]" + if self.url.port is not None and not self.url.is_default_port(): + netloc += ":" + str(self.url.port) + self.headers[hdrs.HOST] = netloc + + if headers: + if isinstance(headers, (dict, MultiDictProxy, MultiDict)): + headers = headers.items() # type: ignore[assignment] + + for key, value in headers: # type: ignore[misc] + # A special case for Host header + if key.lower() == "host": + self.headers[key] = value + else: + self.headers.add(key, value) + + def update_auto_headers(self, skip_auto_headers: Iterable[str]) -> None: + self.skip_auto_headers = CIMultiDict( + (hdr, None) for hdr in sorted(skip_auto_headers) + ) + used_headers = self.headers.copy() + used_headers.extend(self.skip_auto_headers) # type: ignore[arg-type] + + for hdr, val in self.DEFAULT_HEADERS.items(): + if hdr not in used_headers: + self.headers.add(hdr, val) + + if hdrs.USER_AGENT not in used_headers: + self.headers[hdrs.USER_AGENT] = SERVER_SOFTWARE + + def update_cookies(self, cookies: Optional[LooseCookies]) -> None: + """Update request cookies header.""" + if not cookies: + return + + c: SimpleCookie[str] = SimpleCookie() + if hdrs.COOKIE in self.headers: + c.load(self.headers.get(hdrs.COOKIE, "")) + del self.headers[hdrs.COOKIE] + + if isinstance(cookies, Mapping): + iter_cookies = cookies.items() + else: + iter_cookies = cookies # type: ignore[assignment] + for name, value in iter_cookies: + if isinstance(value, Morsel): + # Preserve coded_value + mrsl_val = value.get(value.key, Morsel()) + mrsl_val.set(value.key, value.value, value.coded_value) + c[name] = mrsl_val + else: + c[name] = value # type: ignore[assignment] + + self.headers[hdrs.COOKIE] = c.output(header="", sep=";").strip() + + def update_content_encoding(self, data: Any) -> None: + """Set request content encoding.""" + if data is None: + return + + enc = self.headers.get(hdrs.CONTENT_ENCODING, "").lower() + if enc: + if self.compress: + raise ValueError( + "compress can not be set " "if Content-Encoding header is set" + ) + elif self.compress: + if not isinstance(self.compress, str): + self.compress = "deflate" + self.headers[hdrs.CONTENT_ENCODING] = self.compress + self.chunked = True # enable chunked, no need to deal with length + + def update_transfer_encoding(self) -> None: + """Analyze transfer-encoding header.""" + te = self.headers.get(hdrs.TRANSFER_ENCODING, "").lower() + + if "chunked" in te: + if self.chunked: + raise ValueError( + "chunked can not be set " + 'if "Transfer-Encoding: chunked" header is set' + ) + + elif self.chunked: + if hdrs.CONTENT_LENGTH in self.headers: + raise ValueError( + "chunked can not be set " "if Content-Length header is set" + ) + + self.headers[hdrs.TRANSFER_ENCODING] = "chunked" + else: + if hdrs.CONTENT_LENGTH not in self.headers: + self.headers[hdrs.CONTENT_LENGTH] = str(len(self.body)) + + def update_auth(self, auth: Optional[BasicAuth]) -> None: + """Set basic auth.""" + if auth is None: + auth = self.auth + if auth is None: + return + + if not isinstance(auth, helpers.BasicAuth): + raise TypeError("BasicAuth() tuple is required instead") + + self.headers[hdrs.AUTHORIZATION] = auth.encode() + + def update_body_from_data(self, body: Any) -> None: + if body is None: + return + + # FormData + if isinstance(body, FormData): + body = body() + + try: + body = payload.PAYLOAD_REGISTRY.get(body, disposition=None) + except payload.LookupError: + body = FormData(body)() + + self.body = body + + # enable chunked encoding if needed + if not self.chunked: + if hdrs.CONTENT_LENGTH not in self.headers: + size = body.size + if size is None: + self.chunked = True + else: + if hdrs.CONTENT_LENGTH not in self.headers: + self.headers[hdrs.CONTENT_LENGTH] = str(size) + + # copy payload headers + assert body.headers + for (key, value) in body.headers.items(): + if key in self.headers: + continue + if key in self.skip_auto_headers: + continue + self.headers[key] = value + + def update_expect_continue(self, expect: bool = False) -> None: + if expect: + self.headers[hdrs.EXPECT] = "100-continue" + elif self.headers.get(hdrs.EXPECT, "").lower() == "100-continue": + expect = True + + if expect: + self._continue = self.loop.create_future() + + def update_proxy( + self, + proxy: Optional[URL], + proxy_auth: Optional[BasicAuth], + proxy_headers: Optional[LooseHeaders], + ) -> None: + if proxy_auth and not isinstance(proxy_auth, helpers.BasicAuth): + raise ValueError("proxy_auth must be None or BasicAuth() tuple") + self.proxy = proxy + self.proxy_auth = proxy_auth + self.proxy_headers = proxy_headers + + def keep_alive(self) -> bool: + if self.version < HttpVersion10: + # keep alive not supported at all + return False + if self.version == HttpVersion10: + if self.headers.get(hdrs.CONNECTION) == "keep-alive": + return True + else: # no headers means we close for Http 1.0 + return False + elif self.headers.get(hdrs.CONNECTION) == "close": + return False + + return True + + async def write_bytes( + self, writer: AbstractStreamWriter, conn: "Connection" + ) -> None: + """Support coroutines that yields bytes objects.""" + # 100 response + if self._continue is not None: + await writer.drain() + await self._continue + + protocol = conn.protocol + assert protocol is not None + try: + if isinstance(self.body, payload.Payload): + await self.body.write(writer) + else: + if isinstance(self.body, (bytes, bytearray)): + self.body = (self.body,) # type: ignore[assignment] + + for chunk in self.body: + await writer.write(chunk) # type: ignore[arg-type] + + await writer.write_eof() + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + protocol.set_exception(exc) + else: + new_exc = ClientOSError( + exc.errno, "Can not write request body for %s" % self.url + ) + new_exc.__context__ = exc + new_exc.__cause__ = exc + protocol.set_exception(new_exc) + except asyncio.CancelledError as exc: + if not conn.closed: + protocol.set_exception(exc) + except Exception as exc: + protocol.set_exception(exc) + finally: + self._writer = None + + async def send(self, conn: "Connection") -> "ClientResponse": + # Specify request target: + # - CONNECT request must send authority form URI + # - not CONNECT proxy must send absolute form URI + # - most common is origin form URI + if self.method == hdrs.METH_CONNECT: + connect_host = self.url.raw_host + assert connect_host is not None + if helpers.is_ipv6_address(connect_host): + connect_host = f"[{connect_host}]" + path = f"{connect_host}:{self.url.port}" + elif self.proxy and not self.is_ssl(): + path = str(self.url) + else: + path = self.url.raw_path + if self.url.raw_query_string: + path += "?" + self.url.raw_query_string + + protocol = conn.protocol + assert protocol is not None + writer = StreamWriter( + protocol, + self.loop, + on_chunk_sent=functools.partial( + self._on_chunk_request_sent, self.method, self.url + ), + on_headers_sent=functools.partial( + self._on_headers_request_sent, self.method, self.url + ), + ) + + if self.compress: + writer.enable_compression(self.compress) + + if self.chunked is not None: + writer.enable_chunking() + + # set default content-type + if ( + self.method in self.POST_METHODS + and hdrs.CONTENT_TYPE not in self.skip_auto_headers + and hdrs.CONTENT_TYPE not in self.headers + ): + self.headers[hdrs.CONTENT_TYPE] = "application/octet-stream" + + # set the connection header + connection = self.headers.get(hdrs.CONNECTION) + if not connection: + if self.keep_alive(): + if self.version == HttpVersion10: + connection = "keep-alive" + else: + if self.version == HttpVersion11: + connection = "close" + + if connection is not None: + self.headers[hdrs.CONNECTION] = connection + + # status + headers + status_line = "{0} {1} HTTP/{2[0]}.{2[1]}".format( + self.method, path, self.version + ) + await writer.write_headers(status_line, self.headers) + + self._writer = self.loop.create_task(self.write_bytes(writer, conn)) + + response_class = self.response_class + assert response_class is not None + self.response = response_class( + self.method, + self.original_url, + writer=self._writer, + continue100=self._continue, + timer=self._timer, + request_info=self.request_info, + traces=self._traces, + loop=self.loop, + session=self._session, + ) + return self.response + + async def close(self) -> None: + if self._writer is not None: + try: + await self._writer + finally: + self._writer = None + + def terminate(self) -> None: + if self._writer is not None: + if not self.loop.is_closed(): + self._writer.cancel() + self._writer = None + + async def _on_chunk_request_sent(self, method: str, url: URL, chunk: bytes) -> None: + for trace in self._traces: + await trace.send_request_chunk_sent(method, url, chunk) + + async def _on_headers_request_sent( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + for trace in self._traces: + await trace.send_request_headers(method, url, headers) + + +class ClientResponse(HeadersMixin): + + # from the Status-Line of the response + version = None # HTTP-Version + status: int = None # type: ignore[assignment] # Status-Code + reason = None # Reason-Phrase + + content: StreamReader = None # type: ignore[assignment] # Payload stream + _headers: "CIMultiDictProxy[str]" = None # type: ignore[assignment] + _raw_headers: RawHeaders = None # type: ignore[assignment] # Response raw headers + + _connection = None # current connection + _source_traceback = None + # setted up by ClientRequest after ClientResponse object creation + # post-init stage allows to not change ctor signature + _closed = True # to allow __del__ for non-initialized properly response + _released = False + + def __init__( + self, + method: str, + url: URL, + *, + writer: "asyncio.Task[None]", + continue100: Optional["asyncio.Future[bool]"], + timer: BaseTimerContext, + request_info: RequestInfo, + traces: List["Trace"], + loop: asyncio.AbstractEventLoop, + session: "ClientSession", + ) -> None: + assert isinstance(url, URL) + + self.method = method + self.cookies: SimpleCookie[str] = SimpleCookie() + + self._real_url = url + self._url = url.with_fragment(None) + self._body: Any = None + self._writer: Optional[asyncio.Task[None]] = writer + self._continue = continue100 # None by default + self._closed = True + self._history: Tuple[ClientResponse, ...] = () + self._request_info = request_info + self._timer = timer if timer is not None else TimerNoop() + self._cache: Dict[str, Any] = {} + self._traces = traces + self._loop = loop + # store a reference to session #1985 + self._session: Optional[ClientSession] = session + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + @reify + def url(self) -> URL: + return self._url + + @reify + def url_obj(self) -> URL: + warnings.warn("Deprecated, use .url #1654", DeprecationWarning, stacklevel=2) + return self._url + + @reify + def real_url(self) -> URL: + return self._real_url + + @reify + def host(self) -> str: + assert self._url.host is not None + return self._url.host + + @reify + def headers(self) -> "CIMultiDictProxy[str]": + return self._headers + + @reify + def raw_headers(self) -> RawHeaders: + return self._raw_headers + + @reify + def request_info(self) -> RequestInfo: + return self._request_info + + @reify + def content_disposition(self) -> Optional[ContentDisposition]: + raw = self._headers.get(hdrs.CONTENT_DISPOSITION) + if raw is None: + return None + disposition_type, params_dct = multipart.parse_content_disposition(raw) + params = MappingProxyType(params_dct) + filename = multipart.content_disposition_filename(params) + return ContentDisposition(disposition_type, params, filename) + + def __del__(self, _warnings: Any = warnings) -> None: + if self._closed: + return + + if self._connection is not None: + self._connection.release() + self._cleanup_writer() + + if self._loop.get_debug(): + if PY_36: + kwargs = {"source": self} + else: + kwargs = {} + _warnings.warn(f"Unclosed response {self!r}", ResourceWarning, **kwargs) + context = {"client_response": self, "message": "Unclosed response"} + if self._source_traceback: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + def __repr__(self) -> str: + out = io.StringIO() + ascii_encodable_url = str(self.url) + if self.reason: + ascii_encodable_reason = self.reason.encode( + "ascii", "backslashreplace" + ).decode("ascii") + else: + ascii_encodable_reason = self.reason + print( + "".format( + ascii_encodable_url, self.status, ascii_encodable_reason + ), + file=out, + ) + print(self.headers, file=out) + return out.getvalue() + + @property + def connection(self) -> Optional["Connection"]: + return self._connection + + @reify + def history(self) -> Tuple["ClientResponse", ...]: + """A sequence of of responses, if redirects occurred.""" + return self._history + + @reify + def links(self) -> "MultiDictProxy[MultiDictProxy[Union[str, URL]]]": + links_str = ", ".join(self.headers.getall("link", [])) + + if not links_str: + return MultiDictProxy(MultiDict()) + + links: MultiDict[MultiDictProxy[Union[str, URL]]] = MultiDict() + + for val in re.split(r",(?=\s*<)", links_str): + match = re.match(r"\s*<(.*)>(.*)", val) + if match is None: # pragma: no cover + # the check exists to suppress mypy error + continue + url, params_str = match.groups() + params = params_str.split(";")[1:] + + link: MultiDict[Union[str, URL]] = MultiDict() + + for param in params: + match = re.match(r"^\s*(\S*)\s*=\s*(['\"]?)(.*?)(\2)\s*$", param, re.M) + if match is None: # pragma: no cover + # the check exists to suppress mypy error + continue + key, _, value, _ = match.groups() + + link.add(key, value) + + key = link.get("rel", url) # type: ignore[assignment] + + link.add("url", self.url.join(URL(url))) + + links.add(key, MultiDictProxy(link)) + + return MultiDictProxy(links) + + async def start(self, connection: "Connection") -> "ClientResponse": + """Start response processing.""" + self._closed = False + self._protocol = connection.protocol + self._connection = connection + + with self._timer: + while True: + # read response + try: + protocol = self._protocol + message, payload = await protocol.read() # type: ignore[union-attr] + except http.HttpProcessingError as exc: + raise ClientResponseError( + self.request_info, + self.history, + status=exc.code, + message=exc.message, + headers=exc.headers, + ) from exc + + if message.code < 100 or message.code > 199 or message.code == 101: + break + + if self._continue is not None: + set_result(self._continue, True) + self._continue = None + + # payload eof handler + payload.on_eof(self._response_eof) + + # response status + self.version = message.version + self.status = message.code + self.reason = message.reason + + # headers + self._headers = message.headers # type is CIMultiDictProxy + self._raw_headers = message.raw_headers # type is Tuple[bytes, bytes] + + # payload + self.content = payload + + # cookies + for hdr in self.headers.getall(hdrs.SET_COOKIE, ()): + try: + self.cookies.load(hdr) + except CookieError as exc: + client_logger.warning("Can not load response cookies: %s", exc) + return self + + def _response_eof(self) -> None: + if self._closed: + return + + if self._connection is not None: + # websocket, protocol could be None because + # connection could be detached + if ( + self._connection.protocol is not None + and self._connection.protocol.upgraded + ): + return + + self._connection.release() + self._connection = None + + self._closed = True + self._cleanup_writer() + + @property + def closed(self) -> bool: + return self._closed + + def close(self) -> None: + if not self._released: + self._notify_content() + if self._closed: + return + + self._closed = True + if self._loop is None or self._loop.is_closed(): + return + + if self._connection is not None: + self._connection.close() + self._connection = None + self._cleanup_writer() + + def release(self) -> Any: + if not self._released: + self._notify_content() + if self._closed: + return noop() + + self._closed = True + if self._connection is not None: + self._connection.release() + self._connection = None + + self._cleanup_writer() + return noop() + + @property + def ok(self) -> bool: + """Returns ``True`` if ``status`` is less than ``400``, ``False`` if not. + + This is **not** a check for ``200 OK`` but a check that the response + status is under 400. + """ + return 400 > self.status + + def raise_for_status(self) -> None: + if not self.ok: + # reason should always be not None for a started response + assert self.reason is not None + self.release() + raise ClientResponseError( + self.request_info, + self.history, + status=self.status, + message=self.reason, + headers=self.headers, + ) + + def _cleanup_writer(self) -> None: + if self._writer is not None: + self._writer.cancel() + self._writer = None + self._session = None + + def _notify_content(self) -> None: + content = self.content + if content and content.exception() is None: + content.set_exception(ClientConnectionError("Connection closed")) + self._released = True + + async def wait_for_close(self) -> None: + if self._writer is not None: + try: + await self._writer + finally: + self._writer = None + self.release() + + async def read(self) -> bytes: + """Read response payload.""" + if self._body is None: + try: + self._body = await self.content.read() + for trace in self._traces: + await trace.send_response_chunk_received( + self.method, self.url, self._body + ) + except BaseException: + self.close() + raise + elif self._released: + raise ClientConnectionError("Connection closed") + + return self._body # type: ignore[no-any-return] + + def get_encoding(self) -> str: + ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower() + mimetype = helpers.parse_mimetype(ctype) + + encoding = mimetype.parameters.get("charset") + if encoding: + try: + codecs.lookup(encoding) + except LookupError: + encoding = None + if not encoding: + if mimetype.type == "application" and ( + mimetype.subtype == "json" or mimetype.subtype == "rdap" + ): + # RFC 7159 states that the default encoding is UTF-8. + # RFC 7483 defines application/rdap+json + encoding = "utf-8" + elif self._body is None: + raise RuntimeError( + "Cannot guess the encoding of " "a not yet read body" + ) + else: + encoding = chardet.detect(self._body)["encoding"] + if not encoding: + encoding = "utf-8" + + return encoding + + async def text(self, encoding: Optional[str] = None, errors: str = "strict") -> str: + """Read response payload and decode.""" + if self._body is None: + await self.read() + + if encoding is None: + encoding = self.get_encoding() + + return self._body.decode( # type: ignore[no-any-return,union-attr] + encoding, errors=errors + ) + + async def json( + self, + *, + encoding: Optional[str] = None, + loads: JSONDecoder = DEFAULT_JSON_DECODER, + content_type: Optional[str] = "application/json", + ) -> Any: + """Read and decodes JSON response.""" + if self._body is None: + await self.read() + + if content_type: + ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower() + if not _is_expected_content_type(ctype, content_type): + raise ContentTypeError( + self.request_info, + self.history, + message=( + "Attempt to decode JSON with " "unexpected mimetype: %s" % ctype + ), + headers=self.headers, + ) + + stripped = self._body.strip() # type: ignore[union-attr] + if not stripped: + return None + + if encoding is None: + encoding = self.get_encoding() + + return loads(stripped.decode(encoding)) + + async def __aenter__(self) -> "ClientResponse": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + # similar to _RequestContextManager, we do not need to check + # for exceptions, response object can close connection + # if state is broken + self.release() diff --git a/lib/aiohttp/client_ws.py b/lib/aiohttp/client_ws.py new file mode 100644 index 0000000..9a8ba84 --- /dev/null +++ b/lib/aiohttp/client_ws.py @@ -0,0 +1,300 @@ +"""WebSocket client for asyncio.""" + +import asyncio +from typing import Any, Optional, cast + +import async_timeout + +from .client_exceptions import ClientError +from .client_reqrep import ClientResponse +from .helpers import call_later, set_result +from .http import ( + WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE, + WebSocketError, + WSCloseCode, + WSMessage, + WSMsgType, +) +from .http_websocket import WebSocketWriter # WSMessage +from .streams import EofStream, FlowControlDataQueue +from .typedefs import ( + DEFAULT_JSON_DECODER, + DEFAULT_JSON_ENCODER, + JSONDecoder, + JSONEncoder, +) + + +class ClientWebSocketResponse: + def __init__( + self, + reader: "FlowControlDataQueue[WSMessage]", + writer: WebSocketWriter, + protocol: Optional[str], + response: ClientResponse, + timeout: float, + autoclose: bool, + autoping: bool, + loop: asyncio.AbstractEventLoop, + *, + receive_timeout: Optional[float] = None, + heartbeat: Optional[float] = None, + compress: int = 0, + client_notakeover: bool = False, + ) -> None: + self._response = response + self._conn = response.connection + + self._writer = writer + self._reader = reader + self._protocol = protocol + self._closed = False + self._closing = False + self._close_code: Optional[int] = None + self._timeout = timeout + self._receive_timeout = receive_timeout + self._autoclose = autoclose + self._autoping = autoping + self._heartbeat = heartbeat + self._heartbeat_cb: Optional[asyncio.TimerHandle] = None + if heartbeat is not None: + self._pong_heartbeat = heartbeat / 2.0 + self._pong_response_cb: Optional[asyncio.TimerHandle] = None + self._loop = loop + self._waiting: Optional[asyncio.Future[bool]] = None + self._exception: Optional[BaseException] = None + self._compress = compress + self._client_notakeover = client_notakeover + + self._reset_heartbeat() + + def _cancel_heartbeat(self) -> None: + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = None + + if self._heartbeat_cb is not None: + self._heartbeat_cb.cancel() + self._heartbeat_cb = None + + def _reset_heartbeat(self) -> None: + self._cancel_heartbeat() + + if self._heartbeat is not None: + self._heartbeat_cb = call_later( + self._send_heartbeat, self._heartbeat, self._loop + ) + + def _send_heartbeat(self) -> None: + if self._heartbeat is not None and not self._closed: + # fire-and-forget a task is not perfect but maybe ok for + # sending ping. Otherwise we need a long-living heartbeat + # task in the class. + self._loop.create_task(self._writer.ping()) + + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = call_later( + self._pong_not_received, self._pong_heartbeat, self._loop + ) + + def _pong_not_received(self) -> None: + if not self._closed: + self._closed = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = asyncio.TimeoutError() + self._response.close() + + @property + def closed(self) -> bool: + return self._closed + + @property + def close_code(self) -> Optional[int]: + return self._close_code + + @property + def protocol(self) -> Optional[str]: + return self._protocol + + @property + def compress(self) -> int: + return self._compress + + @property + def client_notakeover(self) -> bool: + return self._client_notakeover + + def get_extra_info(self, name: str, default: Any = None) -> Any: + """extra info from connection transport""" + conn = self._response.connection + if conn is None: + return default + transport = conn.transport + if transport is None: + return default + return transport.get_extra_info(name, default) + + def exception(self) -> Optional[BaseException]: + return self._exception + + async def ping(self, message: bytes = b"") -> None: + await self._writer.ping(message) + + async def pong(self, message: bytes = b"") -> None: + await self._writer.pong(message) + + async def send_str(self, data: str, compress: Optional[int] = None) -> None: + if not isinstance(data, str): + raise TypeError("data argument must be str (%r)" % type(data)) + await self._writer.send(data, binary=False, compress=compress) + + async def send_bytes(self, data: bytes, compress: Optional[int] = None) -> None: + if not isinstance(data, (bytes, bytearray, memoryview)): + raise TypeError("data argument must be byte-ish (%r)" % type(data)) + await self._writer.send(data, binary=True, compress=compress) + + async def send_json( + self, + data: Any, + compress: Optional[int] = None, + *, + dumps: JSONEncoder = DEFAULT_JSON_ENCODER, + ) -> None: + await self.send_str(dumps(data), compress=compress) + + async def close(self, *, code: int = WSCloseCode.OK, message: bytes = b"") -> bool: + # we need to break `receive()` cycle first, + # `close()` may be called from different task + if self._waiting is not None and not self._closed: + self._reader.feed_data(WS_CLOSING_MESSAGE, 0) + await self._waiting + + if not self._closed: + self._cancel_heartbeat() + self._closed = True + try: + await self._writer.close(code, message) + except asyncio.CancelledError: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._response.close() + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + self._response.close() + return True + + if self._closing: + self._response.close() + return True + + while True: + try: + async with async_timeout.timeout(self._timeout): + msg = await self._reader.read() + except asyncio.CancelledError: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._response.close() + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + self._response.close() + return True + + if msg.type == WSMsgType.CLOSE: + self._close_code = msg.data + self._response.close() + return True + else: + return False + + async def receive(self, timeout: Optional[float] = None) -> WSMessage: + while True: + if self._waiting is not None: + raise RuntimeError("Concurrent call to receive() is not allowed") + + if self._closed: + return WS_CLOSED_MESSAGE + elif self._closing: + await self.close() + return WS_CLOSED_MESSAGE + + try: + self._waiting = self._loop.create_future() + try: + async with async_timeout.timeout(timeout or self._receive_timeout): + msg = await self._reader.read() + self._reset_heartbeat() + finally: + waiter = self._waiting + self._waiting = None + set_result(waiter, True) + except (asyncio.CancelledError, asyncio.TimeoutError): + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except EofStream: + self._close_code = WSCloseCode.OK + await self.close() + return WSMessage(WSMsgType.CLOSED, None, None) + except ClientError: + self._closed = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + return WS_CLOSED_MESSAGE + except WebSocketError as exc: + self._close_code = exc.code + await self.close(code=exc.code) + return WSMessage(WSMsgType.ERROR, exc, None) + except Exception as exc: + self._exception = exc + self._closing = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + await self.close() + return WSMessage(WSMsgType.ERROR, exc, None) + + if msg.type == WSMsgType.CLOSE: + self._closing = True + self._close_code = msg.data + if not self._closed and self._autoclose: + await self.close() + elif msg.type == WSMsgType.CLOSING: + self._closing = True + elif msg.type == WSMsgType.PING and self._autoping: + await self.pong(msg.data) + continue + elif msg.type == WSMsgType.PONG and self._autoping: + continue + + return msg + + async def receive_str(self, *, timeout: Optional[float] = None) -> str: + msg = await self.receive(timeout) + if msg.type != WSMsgType.TEXT: + raise TypeError(f"Received message {msg.type}:{msg.data!r} is not str") + return cast(str, msg.data) + + async def receive_bytes(self, *, timeout: Optional[float] = None) -> bytes: + msg = await self.receive(timeout) + if msg.type != WSMsgType.BINARY: + raise TypeError(f"Received message {msg.type}:{msg.data!r} is not bytes") + return cast(bytes, msg.data) + + async def receive_json( + self, + *, + loads: JSONDecoder = DEFAULT_JSON_DECODER, + timeout: Optional[float] = None, + ) -> Any: + data = await self.receive_str(timeout=timeout) + return loads(data) + + def __aiter__(self) -> "ClientWebSocketResponse": + return self + + async def __anext__(self) -> WSMessage: + msg = await self.receive() + if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED): + raise StopAsyncIteration + return msg diff --git a/lib/aiohttp/connector.py b/lib/aiohttp/connector.py new file mode 100644 index 0000000..bf40689 --- /dev/null +++ b/lib/aiohttp/connector.py @@ -0,0 +1,1453 @@ +import asyncio +import functools +import random +import sys +import traceback +import warnings +from collections import defaultdict, deque +from contextlib import suppress +from http.cookies import SimpleCookie +from itertools import cycle, islice +from time import monotonic +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + DefaultDict, + Dict, + Iterator, + List, + Optional, + Set, + Tuple, + Type, + Union, + cast, +) + +import attr + +from . import hdrs, helpers +from .abc import AbstractResolver +from .client_exceptions import ( + ClientConnectionError, + ClientConnectorCertificateError, + ClientConnectorError, + ClientConnectorSSLError, + ClientHttpProxyError, + ClientProxyConnectionError, + ServerFingerprintMismatch, + UnixClientConnectorError, + cert_errors, + ssl_errors, +) +from .client_proto import ResponseHandler +from .client_reqrep import ClientRequest, Fingerprint, _merge_ssl_params +from .helpers import ( + PY_36, + ceil_timeout, + get_running_loop, + is_ip_address, + noop, + sentinel, +) +from .http import RESPONSES +from .locks import EventResultOrError +from .resolver import DefaultResolver + +try: + import ssl + + SSLContext = ssl.SSLContext +except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ("BaseConnector", "TCPConnector", "UnixConnector", "NamedPipeConnector") + + +if TYPE_CHECKING: # pragma: no cover + from .client import ClientTimeout + from .client_reqrep import ConnectionKey + from .tracing import Trace + + +class _DeprecationWaiter: + __slots__ = ("_awaitable", "_awaited") + + def __init__(self, awaitable: Awaitable[Any]) -> None: + self._awaitable = awaitable + self._awaited = False + + def __await__(self) -> Any: + self._awaited = True + return self._awaitable.__await__() + + def __del__(self) -> None: + if not self._awaited: + warnings.warn( + "Connector.close() is a coroutine, " + "please use await connector.close()", + DeprecationWarning, + ) + + +class Connection: + + _source_traceback = None + _transport = None + + def __init__( + self, + connector: "BaseConnector", + key: "ConnectionKey", + protocol: ResponseHandler, + loop: asyncio.AbstractEventLoop, + ) -> None: + self._key = key + self._connector = connector + self._loop = loop + self._protocol: Optional[ResponseHandler] = protocol + self._callbacks: List[Callable[[], None]] = [] + + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + def __repr__(self) -> str: + return f"Connection<{self._key}>" + + def __del__(self, _warnings: Any = warnings) -> None: + if self._protocol is not None: + if PY_36: + kwargs = {"source": self} + else: + kwargs = {} + _warnings.warn(f"Unclosed connection {self!r}", ResourceWarning, **kwargs) + if self._loop.is_closed(): + return + + self._connector._release(self._key, self._protocol, should_close=True) + + context = {"client_connection": self, "message": "Unclosed connection"} + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + @property + def loop(self) -> asyncio.AbstractEventLoop: + warnings.warn( + "connector.loop property is deprecated", DeprecationWarning, stacklevel=2 + ) + return self._loop + + @property + def transport(self) -> Optional[asyncio.Transport]: + if self._protocol is None: + return None + return self._protocol.transport + + @property + def protocol(self) -> Optional[ResponseHandler]: + return self._protocol + + def add_callback(self, callback: Callable[[], None]) -> None: + if callback is not None: + self._callbacks.append(callback) + + def _notify_release(self) -> None: + callbacks, self._callbacks = self._callbacks[:], [] + + for cb in callbacks: + with suppress(Exception): + cb() + + def close(self) -> None: + self._notify_release() + + if self._protocol is not None: + self._connector._release(self._key, self._protocol, should_close=True) + self._protocol = None + + def release(self) -> None: + self._notify_release() + + if self._protocol is not None: + self._connector._release( + self._key, self._protocol, should_close=self._protocol.should_close + ) + self._protocol = None + + @property + def closed(self) -> bool: + return self._protocol is None or not self._protocol.is_connected() + + +class _TransportPlaceholder: + """placeholder for BaseConnector.connect function""" + + def close(self) -> None: + pass + + +class BaseConnector: + """Base connector class. + + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + enable_cleanup_closed - Enables clean-up closed ssl transports. + Disabled by default. + loop - Optional event loop. + """ + + _closed = True # prevent AttributeError in __del__ if ctor was failed + _source_traceback = None + + # abort transport after 2 seconds (cleanup broken connections) + _cleanup_closed_period = 2.0 + + def __init__( + self, + *, + keepalive_timeout: Union[object, None, float] = sentinel, + force_close: bool = False, + limit: int = 100, + limit_per_host: int = 0, + enable_cleanup_closed: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + + if force_close: + if keepalive_timeout is not None and keepalive_timeout is not sentinel: + raise ValueError( + "keepalive_timeout cannot " "be set if force_close is True" + ) + else: + if keepalive_timeout is sentinel: + keepalive_timeout = 15.0 + + loop = get_running_loop(loop) + + self._closed = False + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + self._conns: Dict[ConnectionKey, List[Tuple[ResponseHandler, float]]] = {} + self._limit = limit + self._limit_per_host = limit_per_host + self._acquired: Set[ResponseHandler] = set() + self._acquired_per_host: DefaultDict[ + ConnectionKey, Set[ResponseHandler] + ] = defaultdict(set) + self._keepalive_timeout = cast(float, keepalive_timeout) + self._force_close = force_close + + # {host_key: FIFO list of waiters} + self._waiters = defaultdict(deque) # type: ignore[var-annotated] + + self._loop = loop + self._factory = functools.partial(ResponseHandler, loop=loop) + + self.cookies: SimpleCookie[str] = SimpleCookie() + + # start keep-alive connection cleanup task + self._cleanup_handle: Optional[asyncio.TimerHandle] = None + + # start cleanup closed transports task + self._cleanup_closed_handle: Optional[asyncio.TimerHandle] = None + self._cleanup_closed_disabled = not enable_cleanup_closed + self._cleanup_closed_transports: List[Optional[asyncio.Transport]] = [] + self._cleanup_closed() + + def __del__(self, _warnings: Any = warnings) -> None: + if self._closed: + return + if not self._conns: + return + + conns = [repr(c) for c in self._conns.values()] + + self._close() + + if PY_36: + kwargs = {"source": self} + else: + kwargs = {} + _warnings.warn(f"Unclosed connector {self!r}", ResourceWarning, **kwargs) + context = { + "connector": self, + "connections": conns, + "message": "Unclosed connector", + } + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + def __enter__(self) -> "BaseConnector": + warnings.warn( + '"with Connector():" is deprecated, ' + 'use "async with Connector():" instead', + DeprecationWarning, + ) + return self + + def __exit__(self, *exc: Any) -> None: + self._close() + + async def __aenter__(self) -> "BaseConnector": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]] = None, + exc_value: Optional[BaseException] = None, + exc_traceback: Optional[TracebackType] = None, + ) -> None: + await self.close() + + @property + def force_close(self) -> bool: + """Ultimately close connection on releasing if True.""" + return self._force_close + + @property + def limit(self) -> int: + """The total number for simultaneous connections. + + If limit is 0 the connector has no limit. + The default limit size is 100. + """ + return self._limit + + @property + def limit_per_host(self) -> int: + """The limit for simultaneous connections to the same endpoint. + + Endpoints are the same if they are have equal + (host, port, is_ssl) triple. + """ + return self._limit_per_host + + def _cleanup(self) -> None: + """Cleanup unused transports.""" + if self._cleanup_handle: + self._cleanup_handle.cancel() + # _cleanup_handle should be unset, otherwise _release() will not + # recreate it ever! + self._cleanup_handle = None + + now = self._loop.time() + timeout = self._keepalive_timeout + + if self._conns: + connections = {} + deadline = now - timeout + for key, conns in self._conns.items(): + alive = [] + for proto, use_time in conns: + if proto.is_connected(): + if use_time - deadline < 0: + transport = proto.transport + proto.close() + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + else: + alive.append((proto, use_time)) + else: + transport = proto.transport + proto.close() + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + + if alive: + connections[key] = alive + + self._conns = connections + + if self._conns: + self._cleanup_handle = helpers.weakref_handle( + self, "_cleanup", timeout, self._loop + ) + + def _drop_acquired_per_host( + self, key: "ConnectionKey", val: ResponseHandler + ) -> None: + acquired_per_host = self._acquired_per_host + if key not in acquired_per_host: + return + conns = acquired_per_host[key] + conns.remove(val) + if not conns: + del self._acquired_per_host[key] + + def _cleanup_closed(self) -> None: + """Double confirmation for transport close. + + Some broken ssl servers may leave socket open without proper close. + """ + if self._cleanup_closed_handle: + self._cleanup_closed_handle.cancel() + + for transport in self._cleanup_closed_transports: + if transport is not None: + transport.abort() + + self._cleanup_closed_transports = [] + + if not self._cleanup_closed_disabled: + self._cleanup_closed_handle = helpers.weakref_handle( + self, "_cleanup_closed", self._cleanup_closed_period, self._loop + ) + + def close(self) -> Awaitable[None]: + """Close all opened transports.""" + self._close() + return _DeprecationWaiter(noop()) + + def _close(self) -> None: + if self._closed: + return + + self._closed = True + + try: + if self._loop.is_closed(): + return + + # cancel cleanup task + if self._cleanup_handle: + self._cleanup_handle.cancel() + + # cancel cleanup close task + if self._cleanup_closed_handle: + self._cleanup_closed_handle.cancel() + + for data in self._conns.values(): + for proto, t0 in data: + proto.close() + + for proto in self._acquired: + proto.close() + + for transport in self._cleanup_closed_transports: + if transport is not None: + transport.abort() + + finally: + self._conns.clear() + self._acquired.clear() + self._waiters.clear() + self._cleanup_handle = None + self._cleanup_closed_transports.clear() + self._cleanup_closed_handle = None + + @property + def closed(self) -> bool: + """Is connector closed. + + A readonly property. + """ + return self._closed + + def _available_connections(self, key: "ConnectionKey") -> int: + """ + Return number of available connections. + + The limit, limit_per_host and the connection key are taken into account. + + If it returns less than 1 means that there are no connections + available. + """ + if self._limit: + # total calc available connections + available = self._limit - len(self._acquired) + + # check limit per host + if ( + self._limit_per_host + and available > 0 + and key in self._acquired_per_host + ): + acquired = self._acquired_per_host.get(key) + assert acquired is not None + available = self._limit_per_host - len(acquired) + + elif self._limit_per_host and key in self._acquired_per_host: + # check limit per host + acquired = self._acquired_per_host.get(key) + assert acquired is not None + available = self._limit_per_host - len(acquired) + else: + available = 1 + + return available + + async def connect( + self, req: "ClientRequest", traces: List["Trace"], timeout: "ClientTimeout" + ) -> Connection: + """Get from pool or create new connection.""" + key = req.connection_key + available = self._available_connections(key) + + # Wait if there are no available connections or if there are/were + # waiters (i.e. don't steal connection from a waiter about to wake up) + if available <= 0 or key in self._waiters: + fut = self._loop.create_future() + + # This connection will now count towards the limit. + self._waiters[key].append(fut) + + if traces: + for trace in traces: + await trace.send_connection_queued_start() + + try: + await fut + except BaseException as e: + if key in self._waiters: + # remove a waiter even if it was cancelled, normally it's + # removed when it's notified + try: + self._waiters[key].remove(fut) + except ValueError: # fut may no longer be in list + pass + + raise e + finally: + if key in self._waiters and not self._waiters[key]: + del self._waiters[key] + + if traces: + for trace in traces: + await trace.send_connection_queued_end() + + proto = self._get(key) + if proto is None: + placeholder = cast(ResponseHandler, _TransportPlaceholder()) + self._acquired.add(placeholder) + self._acquired_per_host[key].add(placeholder) + + if traces: + for trace in traces: + await trace.send_connection_create_start() + + try: + proto = await self._create_connection(req, traces, timeout) + if self._closed: + proto.close() + raise ClientConnectionError("Connector is closed.") + except BaseException: + if not self._closed: + self._acquired.remove(placeholder) + self._drop_acquired_per_host(key, placeholder) + self._release_waiter() + raise + else: + if not self._closed: + self._acquired.remove(placeholder) + self._drop_acquired_per_host(key, placeholder) + + if traces: + for trace in traces: + await trace.send_connection_create_end() + else: + if traces: + # Acquire the connection to prevent race conditions with limits + placeholder = cast(ResponseHandler, _TransportPlaceholder()) + self._acquired.add(placeholder) + self._acquired_per_host[key].add(placeholder) + for trace in traces: + await trace.send_connection_reuseconn() + self._acquired.remove(placeholder) + self._drop_acquired_per_host(key, placeholder) + + self._acquired.add(proto) + self._acquired_per_host[key].add(proto) + return Connection(self, key, proto, self._loop) + + def _get(self, key: "ConnectionKey") -> Optional[ResponseHandler]: + try: + conns = self._conns[key] + except KeyError: + return None + + t1 = self._loop.time() + while conns: + proto, t0 = conns.pop() + if proto.is_connected(): + if t1 - t0 > self._keepalive_timeout: + transport = proto.transport + proto.close() + # only for SSL transports + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + else: + if not conns: + # The very last connection was reclaimed: drop the key + del self._conns[key] + return proto + else: + transport = proto.transport + proto.close() + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + + # No more connections: drop the key + del self._conns[key] + return None + + def _release_waiter(self) -> None: + """ + Iterates over all waiters until one to be released is found. + + The one to be released is not finsihed and + belongs to a host that has available connections. + """ + if not self._waiters: + return + + # Having the dict keys ordered this avoids to iterate + # at the same order at each call. + queues = list(self._waiters.keys()) + random.shuffle(queues) + + for key in queues: + if self._available_connections(key) < 1: + continue + + waiters = self._waiters[key] + while waiters: + waiter = waiters.popleft() + if not waiter.done(): + waiter.set_result(None) + return + + def _release_acquired(self, key: "ConnectionKey", proto: ResponseHandler) -> None: + if self._closed: + # acquired connection is already released on connector closing + return + + try: + self._acquired.remove(proto) + self._drop_acquired_per_host(key, proto) + except KeyError: # pragma: no cover + # this may be result of undetermenistic order of objects + # finalization due garbage collection. + pass + else: + self._release_waiter() + + def _release( + self, + key: "ConnectionKey", + protocol: ResponseHandler, + *, + should_close: bool = False, + ) -> None: + if self._closed: + # acquired connection is already released on connector closing + return + + self._release_acquired(key, protocol) + + if self._force_close: + should_close = True + + if should_close or protocol.should_close: + transport = protocol.transport + protocol.close() + + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + else: + conns = self._conns.get(key) + if conns is None: + conns = self._conns[key] = [] + conns.append((protocol, self._loop.time())) + + if self._cleanup_handle is None: + self._cleanup_handle = helpers.weakref_handle( + self, "_cleanup", self._keepalive_timeout, self._loop + ) + + async def _create_connection( + self, req: "ClientRequest", traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + raise NotImplementedError() + + +class _DNSCacheTable: + def __init__(self, ttl: Optional[float] = None) -> None: + self._addrs_rr: Dict[Tuple[str, int], Tuple[Iterator[Dict[str, Any]], int]] = {} + self._timestamps: Dict[Tuple[str, int], float] = {} + self._ttl = ttl + + def __contains__(self, host: object) -> bool: + return host in self._addrs_rr + + def add(self, key: Tuple[str, int], addrs: List[Dict[str, Any]]) -> None: + self._addrs_rr[key] = (cycle(addrs), len(addrs)) + + if self._ttl: + self._timestamps[key] = monotonic() + + def remove(self, key: Tuple[str, int]) -> None: + self._addrs_rr.pop(key, None) + + if self._ttl: + self._timestamps.pop(key, None) + + def clear(self) -> None: + self._addrs_rr.clear() + self._timestamps.clear() + + def next_addrs(self, key: Tuple[str, int]) -> List[Dict[str, Any]]: + loop, length = self._addrs_rr[key] + addrs = list(islice(loop, length)) + # Consume one more element to shift internal state of `cycle` + next(loop) + return addrs + + def expired(self, key: Tuple[str, int]) -> bool: + if self._ttl is None: + return False + + return self._timestamps[key] + self._ttl < monotonic() + + +class TCPConnector(BaseConnector): + """TCP connector. + + verify_ssl - Set to True to check ssl certifications. + fingerprint - Pass the binary sha256 + digest of the expected certificate in DER format to verify + that the certificate the server presents matches. See also + https://en.wikipedia.org/wiki/Transport_Layer_Security#Certificate_pinning + resolver - Enable DNS lookups and use this + resolver + use_dns_cache - Use memory cache for DNS lookups. + ttl_dns_cache - Max seconds having cached a DNS entry, None forever. + family - socket address family + local_addr - local tuple of (host, port) to bind socket to + + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + enable_cleanup_closed - Enables clean-up closed ssl transports. + Disabled by default. + loop - Optional event loop. + """ + + def __init__( + self, + *, + verify_ssl: bool = True, + fingerprint: Optional[bytes] = None, + use_dns_cache: bool = True, + ttl_dns_cache: Optional[int] = 10, + family: int = 0, + ssl_context: Optional[SSLContext] = None, + ssl: Union[None, bool, Fingerprint, SSLContext] = None, + local_addr: Optional[Tuple[str, int]] = None, + resolver: Optional[AbstractResolver] = None, + keepalive_timeout: Union[None, float, object] = sentinel, + force_close: bool = False, + limit: int = 100, + limit_per_host: int = 0, + enable_cleanup_closed: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + ): + super().__init__( + keepalive_timeout=keepalive_timeout, + force_close=force_close, + limit=limit, + limit_per_host=limit_per_host, + enable_cleanup_closed=enable_cleanup_closed, + loop=loop, + ) + + self._ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint) + if resolver is None: + resolver = DefaultResolver(loop=self._loop) + self._resolver = resolver + + self._use_dns_cache = use_dns_cache + self._cached_hosts = _DNSCacheTable(ttl=ttl_dns_cache) + self._throttle_dns_events: Dict[Tuple[str, int], EventResultOrError] = {} + self._family = family + self._local_addr = local_addr + + def close(self) -> Awaitable[None]: + """Close all ongoing DNS calls.""" + for ev in self._throttle_dns_events.values(): + ev.cancel() + + return super().close() + + @property + def family(self) -> int: + """Socket family like AF_INET.""" + return self._family + + @property + def use_dns_cache(self) -> bool: + """True if local DNS caching is enabled.""" + return self._use_dns_cache + + def clear_dns_cache( + self, host: Optional[str] = None, port: Optional[int] = None + ) -> None: + """Remove specified host/port or clear all dns local cache.""" + if host is not None and port is not None: + self._cached_hosts.remove((host, port)) + elif host is not None or port is not None: + raise ValueError("either both host and port " "or none of them are allowed") + else: + self._cached_hosts.clear() + + async def _resolve_host( + self, host: str, port: int, traces: Optional[List["Trace"]] = None + ) -> List[Dict[str, Any]]: + if is_ip_address(host): + return [ + { + "hostname": host, + "host": host, + "port": port, + "family": self._family, + "proto": 0, + "flags": 0, + } + ] + + if not self._use_dns_cache: + + if traces: + for trace in traces: + await trace.send_dns_resolvehost_start(host) + + res = await self._resolver.resolve(host, port, family=self._family) + + if traces: + for trace in traces: + await trace.send_dns_resolvehost_end(host) + + return res + + key = (host, port) + + if (key in self._cached_hosts) and (not self._cached_hosts.expired(key)): + # get result early, before any await (#4014) + result = self._cached_hosts.next_addrs(key) + + if traces: + for trace in traces: + await trace.send_dns_cache_hit(host) + return result + + if key in self._throttle_dns_events: + # get event early, before any await (#4014) + event = self._throttle_dns_events[key] + if traces: + for trace in traces: + await trace.send_dns_cache_hit(host) + await event.wait() + else: + # update dict early, before any await (#4014) + self._throttle_dns_events[key] = EventResultOrError(self._loop) + if traces: + for trace in traces: + await trace.send_dns_cache_miss(host) + try: + + if traces: + for trace in traces: + await trace.send_dns_resolvehost_start(host) + + addrs = await self._resolver.resolve(host, port, family=self._family) + if traces: + for trace in traces: + await trace.send_dns_resolvehost_end(host) + + self._cached_hosts.add(key, addrs) + self._throttle_dns_events[key].set() + except BaseException as e: + # any DNS exception, independently of the implementation + # is set for the waiters to raise the same exception. + self._throttle_dns_events[key].set(exc=e) + raise + finally: + self._throttle_dns_events.pop(key) + + return self._cached_hosts.next_addrs(key) + + async def _create_connection( + self, req: "ClientRequest", traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + """Create connection. + + Has same keyword arguments as BaseEventLoop.create_connection. + """ + if req.proxy: + _, proto = await self._create_proxy_connection(req, traces, timeout) + else: + _, proto = await self._create_direct_connection(req, traces, timeout) + + return proto + + @staticmethod + @functools.lru_cache(None) + def _make_ssl_context(verified: bool) -> SSLContext: + if verified: + return ssl.create_default_context() + else: + sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + sslcontext.options |= ssl.OP_NO_SSLv2 + sslcontext.options |= ssl.OP_NO_SSLv3 + sslcontext.check_hostname = False + sslcontext.verify_mode = ssl.CERT_NONE + try: + sslcontext.options |= ssl.OP_NO_COMPRESSION + except AttributeError as attr_err: + warnings.warn( + "{!s}: The Python interpreter is compiled " + "against OpenSSL < 1.0.0. Ref: " + "https://docs.python.org/3/library/ssl.html" + "#ssl.OP_NO_COMPRESSION".format(attr_err), + ) + sslcontext.set_default_verify_paths() + return sslcontext + + def _get_ssl_context(self, req: "ClientRequest") -> Optional[SSLContext]: + """Logic to get the correct SSL context + + 0. if req.ssl is false, return None + + 1. if ssl_context is specified in req, use it + 2. if _ssl_context is specified in self, use it + 3. otherwise: + 1. if verify_ssl is not specified in req, use self.ssl_context + (will generate a default context according to self.verify_ssl) + 2. if verify_ssl is True in req, generate a default SSL context + 3. if verify_ssl is False in req, generate a SSL context that + won't verify + """ + if req.is_ssl(): + if ssl is None: # pragma: no cover + raise RuntimeError("SSL is not supported.") + sslcontext = req.ssl + if isinstance(sslcontext, ssl.SSLContext): + return sslcontext + if sslcontext is not None: + # not verified or fingerprinted + return self._make_ssl_context(False) + sslcontext = self._ssl + if isinstance(sslcontext, ssl.SSLContext): + return sslcontext + if sslcontext is not None: + # not verified or fingerprinted + return self._make_ssl_context(False) + return self._make_ssl_context(True) + else: + return None + + def _get_fingerprint(self, req: "ClientRequest") -> Optional["Fingerprint"]: + ret = req.ssl + if isinstance(ret, Fingerprint): + return ret + ret = self._ssl + if isinstance(ret, Fingerprint): + return ret + return None + + async def _wrap_create_connection( + self, + *args: Any, + req: "ClientRequest", + timeout: "ClientTimeout", + client_error: Type[Exception] = ClientConnectorError, + **kwargs: Any, + ) -> Tuple[asyncio.Transport, ResponseHandler]: + try: + async with ceil_timeout(timeout.sock_connect): + return await self._loop.create_connection(*args, **kwargs) # type: ignore[return-value] # noqa + except cert_errors as exc: + raise ClientConnectorCertificateError(req.connection_key, exc) from exc + except ssl_errors as exc: + raise ClientConnectorSSLError(req.connection_key, exc) from exc + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise client_error(req.connection_key, exc) from exc + + def _fail_on_no_start_tls(self, req: "ClientRequest") -> None: + """Raise a :py:exc:`RuntimeError` on missing ``start_tls()``. + + One case is that :py:meth:`asyncio.loop.start_tls` is not yet + implemented under Python 3.6. It is necessary for TLS-in-TLS so + that it is possible to send HTTPS queries through HTTPS proxies. + + This doesn't affect regular HTTP requests, though. + """ + if not req.is_ssl(): + return + + proxy_url = req.proxy + assert proxy_url is not None + if proxy_url.scheme != "https": + return + + self._check_loop_for_start_tls() + + def _check_loop_for_start_tls(self) -> None: + try: + self._loop.start_tls + except AttributeError as attr_exc: + raise RuntimeError( + "An HTTPS request is being sent through an HTTPS proxy. " + "This needs support for TLS in TLS but it is not implemented " + "in your runtime for the stdlib asyncio.\n\n" + "Please upgrade to Python 3.7 or higher. For more details, " + "please see:\n" + "* https://bugs.python.org/issue37179\n" + "* https://github.com/python/cpython/pull/28073\n" + "* https://docs.aiohttp.org/en/stable/" + "client_advanced.html#proxy-support\n" + "* https://github.com/aio-libs/aiohttp/discussions/6044\n", + ) from attr_exc + + def _loop_supports_start_tls(self) -> bool: + try: + self._check_loop_for_start_tls() + except RuntimeError: + return False + else: + return True + + def _warn_about_tls_in_tls( + self, + underlying_transport: asyncio.Transport, + req: "ClientRequest", + ) -> None: + """Issue a warning if the requested URL has HTTPS scheme.""" + if req.request_info.url.scheme != "https": + return + + asyncio_supports_tls_in_tls = getattr( + underlying_transport, + "_start_tls_compatible", + False, + ) + + if asyncio_supports_tls_in_tls: + return + + warnings.warn( + "An HTTPS request is being sent through an HTTPS proxy. " + "This support for TLS in TLS is known to be disabled " + "in the stdlib asyncio. This is why you'll probably see " + "an error in the log below.\n\n" + "It is possible to enable it via monkeypatching under " + "Python 3.7 or higher. For more details, see:\n" + "* https://bugs.python.org/issue37179\n" + "* https://github.com/python/cpython/pull/28073\n\n" + "You can temporarily patch this as follows:\n" + "* https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support\n" + "* https://github.com/aio-libs/aiohttp/discussions/6044\n", + RuntimeWarning, + source=self, + # Why `4`? At least 3 of the calls in the stack originate + # from the methods in this class. + stacklevel=3, + ) + + async def _start_tls_connection( + self, + underlying_transport: asyncio.Transport, + req: "ClientRequest", + timeout: "ClientTimeout", + client_error: Type[Exception] = ClientConnectorError, + ) -> Tuple[asyncio.BaseTransport, ResponseHandler]: + """Wrap the raw TCP transport with TLS.""" + tls_proto = self._factory() # Create a brand new proto for TLS + + # Safety of the `cast()` call here is based on the fact that + # internally `_get_ssl_context()` only returns `None` when + # `req.is_ssl()` evaluates to `False` which is never gonna happen + # in this code path. Of course, it's rather fragile + # maintainability-wise but this is to be solved separately. + sslcontext = cast(ssl.SSLContext, self._get_ssl_context(req)) + + try: + async with ceil_timeout(timeout.sock_connect): + try: + tls_transport = await self._loop.start_tls( + underlying_transport, + tls_proto, + sslcontext, + server_hostname=req.host, + ssl_handshake_timeout=timeout.total, + ) + except BaseException: + # We need to close the underlying transport since + # `start_tls()` probably failed before it had a + # chance to do this: + underlying_transport.close() + raise + except cert_errors as exc: + raise ClientConnectorCertificateError(req.connection_key, exc) from exc + except ssl_errors as exc: + raise ClientConnectorSSLError(req.connection_key, exc) from exc + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise client_error(req.connection_key, exc) from exc + except TypeError as type_err: + # Example cause looks like this: + # TypeError: transport is not supported by start_tls() + + raise ClientConnectionError( + "Cannot initialize a TLS-in-TLS connection to host " + f"{req.host!s}:{req.port:d} through an underlying connection " + f"to an HTTPS proxy {req.proxy!s} ssl:{req.ssl or 'default'} " + f"[{type_err!s}]" + ) from type_err + else: + tls_proto.connection_made( + tls_transport + ) # Kick the state machine of the new TLS protocol + + return tls_transport, tls_proto + + async def _create_direct_connection( + self, + req: "ClientRequest", + traces: List["Trace"], + timeout: "ClientTimeout", + *, + client_error: Type[Exception] = ClientConnectorError, + ) -> Tuple[asyncio.Transport, ResponseHandler]: + sslcontext = self._get_ssl_context(req) + fingerprint = self._get_fingerprint(req) + + host = req.url.raw_host + assert host is not None + port = req.port + assert port is not None + host_resolved = asyncio.ensure_future( + self._resolve_host(host, port, traces=traces), loop=self._loop + ) + try: + # Cancelling this lookup should not cancel the underlying lookup + # or else the cancel event will get broadcast to all the waiters + # across all connections. + hosts = await asyncio.shield(host_resolved) + except asyncio.CancelledError: + + def drop_exception(fut: "asyncio.Future[List[Dict[str, Any]]]") -> None: + with suppress(Exception, asyncio.CancelledError): + fut.result() + + host_resolved.add_done_callback(drop_exception) + raise + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + # in case of proxy it is not ClientProxyConnectionError + # it is problem of resolving proxy ip itself + raise ClientConnectorError(req.connection_key, exc) from exc + + last_exc: Optional[Exception] = None + + for hinfo in hosts: + host = hinfo["host"] + port = hinfo["port"] + + try: + transp, proto = await self._wrap_create_connection( + self._factory, + host, + port, + timeout=timeout, + ssl=sslcontext, + family=hinfo["family"], + proto=hinfo["proto"], + flags=hinfo["flags"], + server_hostname=hinfo["hostname"] if sslcontext else None, + local_addr=self._local_addr, + req=req, + client_error=client_error, + ) + except ClientConnectorError as exc: + last_exc = exc + continue + + if req.is_ssl() and fingerprint: + try: + fingerprint.check(transp) + except ServerFingerprintMismatch as exc: + transp.close() + if not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transp) + last_exc = exc + continue + + return transp, proto + else: + assert last_exc is not None + raise last_exc + + async def _create_proxy_connection( + self, req: "ClientRequest", traces: List["Trace"], timeout: "ClientTimeout" + ) -> Tuple[asyncio.BaseTransport, ResponseHandler]: + self._fail_on_no_start_tls(req) + runtime_has_start_tls = self._loop_supports_start_tls() + + headers: Dict[str, str] = {} + if req.proxy_headers is not None: + headers = req.proxy_headers # type: ignore[assignment] + headers[hdrs.HOST] = req.headers[hdrs.HOST] + + url = req.proxy + assert url is not None + proxy_req = ClientRequest( + hdrs.METH_GET, + url, + headers=headers, + auth=req.proxy_auth, + loop=self._loop, + ssl=req.ssl, + ) + + # create connection to proxy server + transport, proto = await self._create_direct_connection( + proxy_req, [], timeout, client_error=ClientProxyConnectionError + ) + + # Many HTTP proxies has buggy keepalive support. Let's not + # reuse connection but close it after processing every + # response. + proto.force_close() + + auth = proxy_req.headers.pop(hdrs.AUTHORIZATION, None) + if auth is not None: + if not req.is_ssl(): + req.headers[hdrs.PROXY_AUTHORIZATION] = auth + else: + proxy_req.headers[hdrs.PROXY_AUTHORIZATION] = auth + + if req.is_ssl(): + if runtime_has_start_tls: + self._warn_about_tls_in_tls(transport, req) + + # For HTTPS requests over HTTP proxy + # we must notify proxy to tunnel connection + # so we send CONNECT command: + # CONNECT www.python.org:443 HTTP/1.1 + # Host: www.python.org + # + # next we must do TLS handshake and so on + # to do this we must wrap raw socket into secure one + # asyncio handles this perfectly + proxy_req.method = hdrs.METH_CONNECT + proxy_req.url = req.url + key = attr.evolve( + req.connection_key, proxy=None, proxy_auth=None, proxy_headers_hash=None + ) + conn = Connection(self, key, proto, self._loop) + proxy_resp = await proxy_req.send(conn) + try: + protocol = conn._protocol + assert protocol is not None + + # read_until_eof=True will ensure the connection isn't closed + # once the response is received and processed allowing + # START_TLS to work on the connection below. + protocol.set_response_params(read_until_eof=runtime_has_start_tls) + resp = await proxy_resp.start(conn) + except BaseException: + proxy_resp.close() + conn.close() + raise + else: + conn._protocol = None + conn._transport = None + try: + if resp.status != 200: + message = resp.reason + if message is None: + message = RESPONSES[resp.status][0] + raise ClientHttpProxyError( + proxy_resp.request_info, + resp.history, + status=resp.status, + message=message, + headers=resp.headers, + ) + if not runtime_has_start_tls: + rawsock = transport.get_extra_info("socket", default=None) + if rawsock is None: + raise RuntimeError( + "Transport does not expose socket instance" + ) + # Duplicate the socket, so now we can close proxy transport + rawsock = rawsock.dup() + except BaseException: + # It shouldn't be closed in `finally` because it's fed to + # `loop.start_tls()` and the docs say not to touch it after + # passing there. + transport.close() + raise + finally: + if not runtime_has_start_tls: + transport.close() + + if not runtime_has_start_tls: + # HTTP proxy with support for upgrade to HTTPS + sslcontext = self._get_ssl_context(req) + return await self._wrap_create_connection( + self._factory, + timeout=timeout, + ssl=sslcontext, + sock=rawsock, + server_hostname=req.host, + req=req, + ) + + return await self._start_tls_connection( + # Access the old transport for the last time before it's + # closed and forgotten forever: + transport, + req=req, + timeout=timeout, + ) + finally: + proxy_resp.close() + + return transport, proto + + +class UnixConnector(BaseConnector): + """Unix socket connector. + + path - Unix socket path. + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + loop - Optional event loop. + """ + + def __init__( + self, + path: str, + force_close: bool = False, + keepalive_timeout: Union[object, float, None] = sentinel, + limit: int = 100, + limit_per_host: int = 0, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + super().__init__( + force_close=force_close, + keepalive_timeout=keepalive_timeout, + limit=limit, + limit_per_host=limit_per_host, + loop=loop, + ) + self._path = path + + @property + def path(self) -> str: + """Path to unix socket.""" + return self._path + + async def _create_connection( + self, req: "ClientRequest", traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + try: + async with ceil_timeout(timeout.sock_connect): + _, proto = await self._loop.create_unix_connection( + self._factory, self._path + ) + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise UnixClientConnectorError(self.path, req.connection_key, exc) from exc + + return cast(ResponseHandler, proto) + + +class NamedPipeConnector(BaseConnector): + """Named pipe connector. + + Only supported by the proactor event loop. + See also: https://docs.python.org/3.7/library/asyncio-eventloop.html + + path - Windows named pipe path. + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + loop - Optional event loop. + """ + + def __init__( + self, + path: str, + force_close: bool = False, + keepalive_timeout: Union[object, float, None] = sentinel, + limit: int = 100, + limit_per_host: int = 0, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + super().__init__( + force_close=force_close, + keepalive_timeout=keepalive_timeout, + limit=limit, + limit_per_host=limit_per_host, + loop=loop, + ) + if not isinstance( + self._loop, asyncio.ProactorEventLoop # type: ignore[attr-defined] + ): + raise RuntimeError( + "Named Pipes only available in proactor " "loop under windows" + ) + self._path = path + + @property + def path(self) -> str: + """Path to the named pipe.""" + return self._path + + async def _create_connection( + self, req: "ClientRequest", traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + try: + async with ceil_timeout(timeout.sock_connect): + _, proto = await self._loop.create_pipe_connection( # type: ignore[attr-defined] # noqa: E501 + self._factory, self._path + ) + # the drain is required so that the connection_made is called + # and transport is set otherwise it is not set before the + # `assert conn.transport is not None` + # in client.py's _request method + await asyncio.sleep(0) + # other option is to manually set transport like + # `proto.transport = trans` + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise ClientConnectorError(req.connection_key, exc) from exc + + return cast(ResponseHandler, proto) diff --git a/lib/aiohttp/cookiejar.py b/lib/aiohttp/cookiejar.py new file mode 100644 index 0000000..6c88b47 --- /dev/null +++ b/lib/aiohttp/cookiejar.py @@ -0,0 +1,415 @@ +import asyncio +import contextlib +import datetime +import os # noqa +import pathlib +import pickle +import re +from collections import defaultdict +from http.cookies import BaseCookie, Morsel, SimpleCookie +from typing import ( # noqa + DefaultDict, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Set, + Tuple, + Union, + cast, +) + +from yarl import URL + +from .abc import AbstractCookieJar, ClearCookiePredicate +from .helpers import is_ip_address, next_whole_second +from .typedefs import LooseCookies, PathLike, StrOrURL + +__all__ = ("CookieJar", "DummyCookieJar") + + +CookieItem = Union[str, "Morsel[str]"] + + +class CookieJar(AbstractCookieJar): + """Implements cookie storage adhering to RFC 6265.""" + + DATE_TOKENS_RE = re.compile( + r"[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]*" + r"(?P[\x00-\x08\x0A-\x1F\d:a-zA-Z\x7F-\xFF]+)" + ) + + DATE_HMS_TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})") + + DATE_DAY_OF_MONTH_RE = re.compile(r"(\d{1,2})") + + DATE_MONTH_RE = re.compile( + "(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|" "(aug)|(sep)|(oct)|(nov)|(dec)", + re.I, + ) + + DATE_YEAR_RE = re.compile(r"(\d{2,4})") + + MAX_TIME = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc) + + MAX_32BIT_TIME = datetime.datetime.utcfromtimestamp(2**31 - 1) + + def __init__( + self, + *, + unsafe: bool = False, + quote_cookie: bool = True, + treat_as_secure_origin: Union[StrOrURL, List[StrOrURL], None] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + super().__init__(loop=loop) + self._cookies: DefaultDict[Tuple[str, str], SimpleCookie[str]] = defaultdict( + SimpleCookie + ) + self._host_only_cookies: Set[Tuple[str, str]] = set() + self._unsafe = unsafe + self._quote_cookie = quote_cookie + if treat_as_secure_origin is None: + treat_as_secure_origin = [] + elif isinstance(treat_as_secure_origin, URL): + treat_as_secure_origin = [treat_as_secure_origin.origin()] + elif isinstance(treat_as_secure_origin, str): + treat_as_secure_origin = [URL(treat_as_secure_origin).origin()] + else: + treat_as_secure_origin = [ + URL(url).origin() if isinstance(url, str) else url.origin() + for url in treat_as_secure_origin + ] + self._treat_as_secure_origin = treat_as_secure_origin + self._next_expiration = next_whole_second() + self._expirations: Dict[Tuple[str, str, str], datetime.datetime] = {} + # #4515: datetime.max may not be representable on 32-bit platforms + self._max_time = self.MAX_TIME + try: + self._max_time.timestamp() + except OverflowError: + self._max_time = self.MAX_32BIT_TIME + + def save(self, file_path: PathLike) -> None: + file_path = pathlib.Path(file_path) + with file_path.open(mode="wb") as f: + pickle.dump(self._cookies, f, pickle.HIGHEST_PROTOCOL) + + def load(self, file_path: PathLike) -> None: + file_path = pathlib.Path(file_path) + with file_path.open(mode="rb") as f: + self._cookies = pickle.load(f) + + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + if predicate is None: + self._next_expiration = next_whole_second() + self._cookies.clear() + self._host_only_cookies.clear() + self._expirations.clear() + return + + to_del = [] + now = datetime.datetime.now(datetime.timezone.utc) + for (domain, path), cookie in self._cookies.items(): + for name, morsel in cookie.items(): + key = (domain, path, name) + if ( + key in self._expirations and self._expirations[key] <= now + ) or predicate(morsel): + to_del.append(key) + + for domain, path, name in to_del: + self._host_only_cookies.discard((domain, name)) + key = (domain, path, name) + if key in self._expirations: + del self._expirations[(domain, path, name)] + self._cookies[(domain, path)].pop(name, None) + + next_expiration = min(self._expirations.values(), default=self._max_time) + try: + self._next_expiration = next_expiration.replace( + microsecond=0 + ) + datetime.timedelta(seconds=1) + except OverflowError: + self._next_expiration = self._max_time + + def clear_domain(self, domain: str) -> None: + self.clear(lambda x: self._is_domain_match(domain, x["domain"])) + + def __iter__(self) -> "Iterator[Morsel[str]]": + self._do_expiration() + for val in self._cookies.values(): + yield from val.values() + + def __len__(self) -> int: + return sum(1 for i in self) + + def _do_expiration(self) -> None: + self.clear(lambda x: False) + + def _expire_cookie( + self, when: datetime.datetime, domain: str, path: str, name: str + ) -> None: + self._next_expiration = min(self._next_expiration, when) + self._expirations[(domain, path, name)] = when + + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + """Update cookies.""" + hostname = response_url.raw_host + + if not self._unsafe and is_ip_address(hostname): + # Don't accept cookies from IPs + return + + if isinstance(cookies, Mapping): + cookies = cookies.items() + + for name, cookie in cookies: + if not isinstance(cookie, Morsel): + tmp: SimpleCookie[str] = SimpleCookie() + tmp[name] = cookie # type: ignore[assignment] + cookie = tmp[name] + + domain = cookie["domain"] + + # ignore domains with trailing dots + if domain.endswith("."): + domain = "" + del cookie["domain"] + + if not domain and hostname is not None: + # Set the cookie's domain to the response hostname + # and set its host-only-flag + self._host_only_cookies.add((hostname, name)) + domain = cookie["domain"] = hostname + + if domain.startswith("."): + # Remove leading dot + domain = domain[1:] + cookie["domain"] = domain + + if hostname and not self._is_domain_match(domain, hostname): + # Setting cookies for different domains is not allowed + continue + + path = cookie["path"] + if not path or not path.startswith("/"): + # Set the cookie's path to the response path + path = response_url.path + if not path.startswith("/"): + path = "/" + else: + # Cut everything from the last slash to the end + path = "/" + path[1 : path.rfind("/")] + cookie["path"] = path + + max_age = cookie["max-age"] + if max_age: + try: + delta_seconds = int(max_age) + try: + max_age_expiration = datetime.datetime.now( + datetime.timezone.utc + ) + datetime.timedelta(seconds=delta_seconds) + except OverflowError: + max_age_expiration = self._max_time + self._expire_cookie(max_age_expiration, domain, path, name) + except ValueError: + cookie["max-age"] = "" + + else: + expires = cookie["expires"] + if expires: + expire_time = self._parse_date(expires) + if expire_time: + self._expire_cookie(expire_time, domain, path, name) + else: + cookie["expires"] = "" + + self._cookies[(domain, path)][name] = cookie + + self._do_expiration() + + def filter_cookies( + self, request_url: URL = URL() + ) -> Union["BaseCookie[str]", "SimpleCookie[str]"]: + """Returns this jar's cookies filtered by their attributes.""" + self._do_expiration() + request_url = URL(request_url) + filtered: Union["SimpleCookie[str]", "BaseCookie[str]"] = ( + SimpleCookie() if self._quote_cookie else BaseCookie() + ) + hostname = request_url.raw_host or "" + request_origin = URL() + with contextlib.suppress(ValueError): + request_origin = request_url.origin() + + is_not_secure = ( + request_url.scheme not in ("https", "wss") + and request_origin not in self._treat_as_secure_origin + ) + + for cookie in self: + name = cookie.key + domain = cookie["domain"] + + # Send shared cookies + if not domain: + filtered[name] = cookie.value + continue + + if not self._unsafe and is_ip_address(hostname): + continue + + if (domain, name) in self._host_only_cookies: + if domain != hostname: + continue + elif not self._is_domain_match(domain, hostname): + continue + + if not self._is_path_match(request_url.path, cookie["path"]): + continue + + if is_not_secure and cookie["secure"]: + continue + + # It's critical we use the Morsel so the coded_value + # (based on cookie version) is preserved + mrsl_val = cast("Morsel[str]", cookie.get(cookie.key, Morsel())) + mrsl_val.set(cookie.key, cookie.value, cookie.coded_value) + filtered[name] = mrsl_val + + return filtered + + @staticmethod + def _is_domain_match(domain: str, hostname: str) -> bool: + """Implements domain matching adhering to RFC 6265.""" + if hostname == domain: + return True + + if not hostname.endswith(domain): + return False + + non_matching = hostname[: -len(domain)] + + if not non_matching.endswith("."): + return False + + return not is_ip_address(hostname) + + @staticmethod + def _is_path_match(req_path: str, cookie_path: str) -> bool: + """Implements path matching adhering to RFC 6265.""" + if not req_path.startswith("/"): + req_path = "/" + + if req_path == cookie_path: + return True + + if not req_path.startswith(cookie_path): + return False + + if cookie_path.endswith("/"): + return True + + non_matching = req_path[len(cookie_path) :] + + return non_matching.startswith("/") + + @classmethod + def _parse_date(cls, date_str: str) -> Optional[datetime.datetime]: + """Implements date string parsing adhering to RFC 6265.""" + if not date_str: + return None + + found_time = False + found_day = False + found_month = False + found_year = False + + hour = minute = second = 0 + day = 0 + month = 0 + year = 0 + + for token_match in cls.DATE_TOKENS_RE.finditer(date_str): + + token = token_match.group("token") + + if not found_time: + time_match = cls.DATE_HMS_TIME_RE.match(token) + if time_match: + found_time = True + hour, minute, second = (int(s) for s in time_match.groups()) + continue + + if not found_day: + day_match = cls.DATE_DAY_OF_MONTH_RE.match(token) + if day_match: + found_day = True + day = int(day_match.group()) + continue + + if not found_month: + month_match = cls.DATE_MONTH_RE.match(token) + if month_match: + found_month = True + assert month_match.lastindex is not None + month = month_match.lastindex + continue + + if not found_year: + year_match = cls.DATE_YEAR_RE.match(token) + if year_match: + found_year = True + year = int(year_match.group()) + + if 70 <= year <= 99: + year += 1900 + elif 0 <= year <= 69: + year += 2000 + + if False in (found_day, found_month, found_year, found_time): + return None + + if not 1 <= day <= 31: + return None + + if year < 1601 or hour > 23 or minute > 59 or second > 59: + return None + + return datetime.datetime( + year, month, day, hour, minute, second, tzinfo=datetime.timezone.utc + ) + + +class DummyCookieJar(AbstractCookieJar): + """Implements a dummy cookie storage. + + It can be used with the ClientSession when no cookie processing is needed. + + """ + + def __init__(self, *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: + super().__init__(loop=loop) + + def __iter__(self) -> "Iterator[Morsel[str]]": + while False: + yield None + + def __len__(self) -> int: + return 0 + + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + pass + + def clear_domain(self, domain: str) -> None: + pass + + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + pass + + def filter_cookies(self, request_url: URL) -> "BaseCookie[str]": + return SimpleCookie() diff --git a/lib/aiohttp/formdata.py b/lib/aiohttp/formdata.py new file mode 100644 index 0000000..e7cd24c --- /dev/null +++ b/lib/aiohttp/formdata.py @@ -0,0 +1,172 @@ +import io +from typing import Any, Iterable, List, Optional +from urllib.parse import urlencode + +from multidict import MultiDict, MultiDictProxy + +from . import hdrs, multipart, payload +from .helpers import guess_filename +from .payload import Payload + +__all__ = ("FormData",) + + +class FormData: + """Helper class for form body generation. + + Supports multipart/form-data and application/x-www-form-urlencoded. + """ + + def __init__( + self, + fields: Iterable[Any] = (), + quote_fields: bool = True, + charset: Optional[str] = None, + ) -> None: + self._writer = multipart.MultipartWriter("form-data") + self._fields: List[Any] = [] + self._is_multipart = False + self._is_processed = False + self._quote_fields = quote_fields + self._charset = charset + + if isinstance(fields, dict): + fields = list(fields.items()) + elif not isinstance(fields, (list, tuple)): + fields = (fields,) + self.add_fields(*fields) + + @property + def is_multipart(self) -> bool: + return self._is_multipart + + def add_field( + self, + name: str, + value: Any, + *, + content_type: Optional[str] = None, + filename: Optional[str] = None, + content_transfer_encoding: Optional[str] = None, + ) -> None: + + if isinstance(value, io.IOBase): + self._is_multipart = True + elif isinstance(value, (bytes, bytearray, memoryview)): + if filename is None and content_transfer_encoding is None: + filename = name + + type_options: MultiDict[str] = MultiDict({"name": name}) + if filename is not None and not isinstance(filename, str): + raise TypeError( + "filename must be an instance of str. " "Got: %s" % filename + ) + if filename is None and isinstance(value, io.IOBase): + filename = guess_filename(value, name) + if filename is not None: + type_options["filename"] = filename + self._is_multipart = True + + headers = {} + if content_type is not None: + if not isinstance(content_type, str): + raise TypeError( + "content_type must be an instance of str. " "Got: %s" % content_type + ) + headers[hdrs.CONTENT_TYPE] = content_type + self._is_multipart = True + if content_transfer_encoding is not None: + if not isinstance(content_transfer_encoding, str): + raise TypeError( + "content_transfer_encoding must be an instance" + " of str. Got: %s" % content_transfer_encoding + ) + headers[hdrs.CONTENT_TRANSFER_ENCODING] = content_transfer_encoding + self._is_multipart = True + + self._fields.append((type_options, headers, value)) + + def add_fields(self, *fields: Any) -> None: + to_add = list(fields) + + while to_add: + rec = to_add.pop(0) + + if isinstance(rec, io.IOBase): + k = guess_filename(rec, "unknown") + self.add_field(k, rec) # type: ignore[arg-type] + + elif isinstance(rec, (MultiDictProxy, MultiDict)): + to_add.extend(rec.items()) + + elif isinstance(rec, (list, tuple)) and len(rec) == 2: + k, fp = rec + self.add_field(k, fp) # type: ignore[arg-type] + + else: + raise TypeError( + "Only io.IOBase, multidict and (name, file) " + "pairs allowed, use .add_field() for passing " + "more complex parameters, got {!r}".format(rec) + ) + + def _gen_form_urlencoded(self) -> payload.BytesPayload: + # form data (x-www-form-urlencoded) + data = [] + for type_options, _, value in self._fields: + data.append((type_options["name"], value)) + + charset = self._charset if self._charset is not None else "utf-8" + + if charset == "utf-8": + content_type = "application/x-www-form-urlencoded" + else: + content_type = "application/x-www-form-urlencoded; " "charset=%s" % charset + + return payload.BytesPayload( + urlencode(data, doseq=True, encoding=charset).encode(), + content_type=content_type, + ) + + def _gen_form_data(self) -> multipart.MultipartWriter: + """Encode a list of fields using the multipart/form-data MIME format""" + if self._is_processed: + raise RuntimeError("Form data has been processed already") + for dispparams, headers, value in self._fields: + try: + if hdrs.CONTENT_TYPE in headers: + part = payload.get_payload( + value, + content_type=headers[hdrs.CONTENT_TYPE], + headers=headers, + encoding=self._charset, + ) + else: + part = payload.get_payload( + value, headers=headers, encoding=self._charset + ) + except Exception as exc: + raise TypeError( + "Can not serialize value type: %r\n " + "headers: %r\n value: %r" % (type(value), headers, value) + ) from exc + + if dispparams: + part.set_content_disposition( + "form-data", quote_fields=self._quote_fields, **dispparams + ) + # FIXME cgi.FieldStorage doesn't likes body parts with + # Content-Length which were sent via chunked transfer encoding + assert part.headers is not None + part.headers.popall(hdrs.CONTENT_LENGTH, None) + + self._writer.append_payload(part) + + self._is_processed = True + return self._writer + + def __call__(self) -> Payload: + if self._is_multipart: + return self._gen_form_data() + else: + return self._gen_form_urlencoded() diff --git a/lib/aiohttp/hdrs.py b/lib/aiohttp/hdrs.py new file mode 100644 index 0000000..a619f25 --- /dev/null +++ b/lib/aiohttp/hdrs.py @@ -0,0 +1,114 @@ +"""HTTP Headers constants.""" + +# After changing the file content call ./tools/gen.py +# to regenerate the headers parser +import sys +from typing import Set + +from multidict import istr + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + +METH_ANY: Final[str] = "*" +METH_CONNECT: Final[str] = "CONNECT" +METH_HEAD: Final[str] = "HEAD" +METH_GET: Final[str] = "GET" +METH_DELETE: Final[str] = "DELETE" +METH_OPTIONS: Final[str] = "OPTIONS" +METH_PATCH: Final[str] = "PATCH" +METH_POST: Final[str] = "POST" +METH_PUT: Final[str] = "PUT" +METH_TRACE: Final[str] = "TRACE" + +METH_ALL: Final[Set[str]] = { + METH_CONNECT, + METH_HEAD, + METH_GET, + METH_DELETE, + METH_OPTIONS, + METH_PATCH, + METH_POST, + METH_PUT, + METH_TRACE, +} + +ACCEPT: Final[istr] = istr("Accept") +ACCEPT_CHARSET: Final[istr] = istr("Accept-Charset") +ACCEPT_ENCODING: Final[istr] = istr("Accept-Encoding") +ACCEPT_LANGUAGE: Final[istr] = istr("Accept-Language") +ACCEPT_RANGES: Final[istr] = istr("Accept-Ranges") +ACCESS_CONTROL_MAX_AGE: Final[istr] = istr("Access-Control-Max-Age") +ACCESS_CONTROL_ALLOW_CREDENTIALS: Final[istr] = istr("Access-Control-Allow-Credentials") +ACCESS_CONTROL_ALLOW_HEADERS: Final[istr] = istr("Access-Control-Allow-Headers") +ACCESS_CONTROL_ALLOW_METHODS: Final[istr] = istr("Access-Control-Allow-Methods") +ACCESS_CONTROL_ALLOW_ORIGIN: Final[istr] = istr("Access-Control-Allow-Origin") +ACCESS_CONTROL_EXPOSE_HEADERS: Final[istr] = istr("Access-Control-Expose-Headers") +ACCESS_CONTROL_REQUEST_HEADERS: Final[istr] = istr("Access-Control-Request-Headers") +ACCESS_CONTROL_REQUEST_METHOD: Final[istr] = istr("Access-Control-Request-Method") +AGE: Final[istr] = istr("Age") +ALLOW: Final[istr] = istr("Allow") +AUTHORIZATION: Final[istr] = istr("Authorization") +CACHE_CONTROL: Final[istr] = istr("Cache-Control") +CONNECTION: Final[istr] = istr("Connection") +CONTENT_DISPOSITION: Final[istr] = istr("Content-Disposition") +CONTENT_ENCODING: Final[istr] = istr("Content-Encoding") +CONTENT_LANGUAGE: Final[istr] = istr("Content-Language") +CONTENT_LENGTH: Final[istr] = istr("Content-Length") +CONTENT_LOCATION: Final[istr] = istr("Content-Location") +CONTENT_MD5: Final[istr] = istr("Content-MD5") +CONTENT_RANGE: Final[istr] = istr("Content-Range") +CONTENT_TRANSFER_ENCODING: Final[istr] = istr("Content-Transfer-Encoding") +CONTENT_TYPE: Final[istr] = istr("Content-Type") +COOKIE: Final[istr] = istr("Cookie") +DATE: Final[istr] = istr("Date") +DESTINATION: Final[istr] = istr("Destination") +DIGEST: Final[istr] = istr("Digest") +ETAG: Final[istr] = istr("Etag") +EXPECT: Final[istr] = istr("Expect") +EXPIRES: Final[istr] = istr("Expires") +FORWARDED: Final[istr] = istr("Forwarded") +FROM: Final[istr] = istr("From") +HOST: Final[istr] = istr("Host") +IF_MATCH: Final[istr] = istr("If-Match") +IF_MODIFIED_SINCE: Final[istr] = istr("If-Modified-Since") +IF_NONE_MATCH: Final[istr] = istr("If-None-Match") +IF_RANGE: Final[istr] = istr("If-Range") +IF_UNMODIFIED_SINCE: Final[istr] = istr("If-Unmodified-Since") +KEEP_ALIVE: Final[istr] = istr("Keep-Alive") +LAST_EVENT_ID: Final[istr] = istr("Last-Event-ID") +LAST_MODIFIED: Final[istr] = istr("Last-Modified") +LINK: Final[istr] = istr("Link") +LOCATION: Final[istr] = istr("Location") +MAX_FORWARDS: Final[istr] = istr("Max-Forwards") +ORIGIN: Final[istr] = istr("Origin") +PRAGMA: Final[istr] = istr("Pragma") +PROXY_AUTHENTICATE: Final[istr] = istr("Proxy-Authenticate") +PROXY_AUTHORIZATION: Final[istr] = istr("Proxy-Authorization") +RANGE: Final[istr] = istr("Range") +REFERER: Final[istr] = istr("Referer") +RETRY_AFTER: Final[istr] = istr("Retry-After") +SEC_WEBSOCKET_ACCEPT: Final[istr] = istr("Sec-WebSocket-Accept") +SEC_WEBSOCKET_VERSION: Final[istr] = istr("Sec-WebSocket-Version") +SEC_WEBSOCKET_PROTOCOL: Final[istr] = istr("Sec-WebSocket-Protocol") +SEC_WEBSOCKET_EXTENSIONS: Final[istr] = istr("Sec-WebSocket-Extensions") +SEC_WEBSOCKET_KEY: Final[istr] = istr("Sec-WebSocket-Key") +SEC_WEBSOCKET_KEY1: Final[istr] = istr("Sec-WebSocket-Key1") +SERVER: Final[istr] = istr("Server") +SET_COOKIE: Final[istr] = istr("Set-Cookie") +TE: Final[istr] = istr("TE") +TRAILER: Final[istr] = istr("Trailer") +TRANSFER_ENCODING: Final[istr] = istr("Transfer-Encoding") +UPGRADE: Final[istr] = istr("Upgrade") +URI: Final[istr] = istr("URI") +USER_AGENT: Final[istr] = istr("User-Agent") +VARY: Final[istr] = istr("Vary") +VIA: Final[istr] = istr("Via") +WANT_DIGEST: Final[istr] = istr("Want-Digest") +WARNING: Final[istr] = istr("Warning") +WWW_AUTHENTICATE: Final[istr] = istr("WWW-Authenticate") +X_FORWARDED_FOR: Final[istr] = istr("X-Forwarded-For") +X_FORWARDED_HOST: Final[istr] = istr("X-Forwarded-Host") +X_FORWARDED_PROTO: Final[istr] = istr("X-Forwarded-Proto") diff --git a/lib/aiohttp/helpers.py b/lib/aiohttp/helpers.py new file mode 100644 index 0000000..874ab1a --- /dev/null +++ b/lib/aiohttp/helpers.py @@ -0,0 +1,878 @@ +"""Various helper functions""" + +import asyncio +import base64 +import binascii +import datetime +import functools +import inspect +import netrc +import os +import platform +import re +import sys +import time +import warnings +import weakref +from collections import namedtuple +from contextlib import suppress +from email.parser import HeaderParser +from email.utils import parsedate +from math import ceil +from pathlib import Path +from types import TracebackType +from typing import ( + Any, + Callable, + ContextManager, + Dict, + Generator, + Generic, + Iterable, + Iterator, + List, + Mapping, + Optional, + Pattern, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) +from urllib.parse import quote +from urllib.request import getproxies, proxy_bypass + +import async_timeout +import attr +from multidict import MultiDict, MultiDictProxy +from yarl import URL + +from . import hdrs +from .log import client_logger, internal_logger +from .typedefs import PathLike, Protocol # noqa + +__all__ = ("BasicAuth", "ChainMapProxy", "ETag") + +IS_MACOS = platform.system() == "Darwin" +IS_WINDOWS = platform.system() == "Windows" + +PY_36 = sys.version_info >= (3, 6) +PY_37 = sys.version_info >= (3, 7) +PY_38 = sys.version_info >= (3, 8) +PY_310 = sys.version_info >= (3, 10) +PY_311 = sys.version_info >= (3, 11) + +if sys.version_info < (3, 7): + import idna_ssl + + idna_ssl.patch_match_hostname() + + def all_tasks( + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> Set["asyncio.Task[Any]"]: + tasks = list(asyncio.Task.all_tasks(loop)) + return {t for t in tasks if not t.done()} + +else: + all_tasks = asyncio.all_tasks + + +_T = TypeVar("_T") +_S = TypeVar("_S") + + +sentinel: Any = object() +NO_EXTENSIONS: bool = bool(os.environ.get("AIOHTTP_NO_EXTENSIONS")) + +# N.B. sys.flags.dev_mode is available on Python 3.7+, use getattr +# for compatibility with older versions +DEBUG: bool = getattr(sys.flags, "dev_mode", False) or ( + not sys.flags.ignore_environment and bool(os.environ.get("PYTHONASYNCIODEBUG")) +) + + +CHAR = {chr(i) for i in range(0, 128)} +CTL = {chr(i) for i in range(0, 32)} | { + chr(127), +} +SEPARATORS = { + "(", + ")", + "<", + ">", + "@", + ",", + ";", + ":", + "\\", + '"', + "/", + "[", + "]", + "?", + "=", + "{", + "}", + " ", + chr(9), +} +TOKEN = CHAR ^ CTL ^ SEPARATORS + + +class noop: + def __await__(self) -> Generator[None, None, None]: + yield + + +class BasicAuth(namedtuple("BasicAuth", ["login", "password", "encoding"])): + """Http basic authentication helper.""" + + def __new__( + cls, login: str, password: str = "", encoding: str = "latin1" + ) -> "BasicAuth": + if login is None: + raise ValueError("None is not allowed as login value") + + if password is None: + raise ValueError("None is not allowed as password value") + + if ":" in login: + raise ValueError('A ":" is not allowed in login (RFC 1945#section-11.1)') + + return super().__new__(cls, login, password, encoding) + + @classmethod + def decode(cls, auth_header: str, encoding: str = "latin1") -> "BasicAuth": + """Create a BasicAuth object from an Authorization HTTP header.""" + try: + auth_type, encoded_credentials = auth_header.split(" ", 1) + except ValueError: + raise ValueError("Could not parse authorization header.") + + if auth_type.lower() != "basic": + raise ValueError("Unknown authorization method %s" % auth_type) + + try: + decoded = base64.b64decode( + encoded_credentials.encode("ascii"), validate=True + ).decode(encoding) + except binascii.Error: + raise ValueError("Invalid base64 encoding.") + + try: + # RFC 2617 HTTP Authentication + # https://www.ietf.org/rfc/rfc2617.txt + # the colon must be present, but the username and password may be + # otherwise blank. + username, password = decoded.split(":", 1) + except ValueError: + raise ValueError("Invalid credentials.") + + return cls(username, password, encoding=encoding) + + @classmethod + def from_url(cls, url: URL, *, encoding: str = "latin1") -> Optional["BasicAuth"]: + """Create BasicAuth from url.""" + if not isinstance(url, URL): + raise TypeError("url should be yarl.URL instance") + if url.user is None: + return None + return cls(url.user, url.password or "", encoding=encoding) + + def encode(self) -> str: + """Encode credentials.""" + creds = (f"{self.login}:{self.password}").encode(self.encoding) + return "Basic %s" % base64.b64encode(creds).decode(self.encoding) + + +def strip_auth_from_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]: + auth = BasicAuth.from_url(url) + if auth is None: + return url, None + else: + return url.with_user(None), auth + + +def netrc_from_env() -> Optional[netrc.netrc]: + """Load netrc from file. + + Attempt to load it from the path specified by the env-var + NETRC or in the default location in the user's home directory. + + Returns None if it couldn't be found or fails to parse. + """ + netrc_env = os.environ.get("NETRC") + + if netrc_env is not None: + netrc_path = Path(netrc_env) + else: + try: + home_dir = Path.home() + except RuntimeError as e: # pragma: no cover + # if pathlib can't resolve home, it may raise a RuntimeError + client_logger.debug( + "Could not resolve home directory when " + "trying to look for .netrc file: %s", + e, + ) + return None + + netrc_path = home_dir / ("_netrc" if IS_WINDOWS else ".netrc") + + try: + return netrc.netrc(str(netrc_path)) + except netrc.NetrcParseError as e: + client_logger.warning("Could not parse .netrc file: %s", e) + except OSError as e: + # we couldn't read the file (doesn't exist, permissions, etc.) + if netrc_env or netrc_path.is_file(): + # only warn if the environment wanted us to load it, + # or it appears like the default file does actually exist + client_logger.warning("Could not read .netrc file: %s", e) + + return None + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class ProxyInfo: + proxy: URL + proxy_auth: Optional[BasicAuth] + + +def proxies_from_env() -> Dict[str, ProxyInfo]: + proxy_urls = { + k: URL(v) + for k, v in getproxies().items() + if k in ("http", "https", "ws", "wss") + } + netrc_obj = netrc_from_env() + stripped = {k: strip_auth_from_url(v) for k, v in proxy_urls.items()} + ret = {} + for proto, val in stripped.items(): + proxy, auth = val + if proxy.scheme in ("https", "wss"): + client_logger.warning( + "%s proxies %s are not supported, ignoring", proxy.scheme.upper(), proxy + ) + continue + if netrc_obj and auth is None: + auth_from_netrc = None + if proxy.host is not None: + auth_from_netrc = netrc_obj.authenticators(proxy.host) + if auth_from_netrc is not None: + # auth_from_netrc is a (`user`, `account`, `password`) tuple, + # `user` and `account` both can be username, + # if `user` is None, use `account` + *logins, password = auth_from_netrc + login = logins[0] if logins[0] else logins[-1] + auth = BasicAuth(cast(str, login), cast(str, password)) + ret[proto] = ProxyInfo(proxy, auth) + return ret + + +def current_task( + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> "Optional[asyncio.Task[Any]]": + if sys.version_info >= (3, 7): + return asyncio.current_task(loop=loop) + else: + return asyncio.Task.current_task(loop=loop) + + +def get_running_loop( + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> asyncio.AbstractEventLoop: + if loop is None: + loop = asyncio.get_event_loop() + if not loop.is_running(): + warnings.warn( + "The object should be created within an async function", + DeprecationWarning, + stacklevel=3, + ) + if loop.get_debug(): + internal_logger.warning( + "The object should be created within an async function", stack_info=True + ) + return loop + + +def isasyncgenfunction(obj: Any) -> bool: + func = getattr(inspect, "isasyncgenfunction", None) + if func is not None: + return func(obj) # type: ignore[no-any-return] + else: + return False + + +def get_env_proxy_for_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]: + """Get a permitted proxy for the given URL from the env.""" + if url.host is not None and proxy_bypass(url.host): + raise LookupError(f"Proxying is disallowed for `{url.host!r}`") + + proxies_in_env = proxies_from_env() + try: + proxy_info = proxies_in_env[url.scheme] + except KeyError: + raise LookupError(f"No proxies found for `{url!s}` in the env") + else: + return proxy_info.proxy, proxy_info.proxy_auth + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class MimeType: + type: str + subtype: str + suffix: str + parameters: "MultiDictProxy[str]" + + +@functools.lru_cache(maxsize=56) +def parse_mimetype(mimetype: str) -> MimeType: + """Parses a MIME type into its components. + + mimetype is a MIME type string. + + Returns a MimeType object. + + Example: + + >>> parse_mimetype('text/html; charset=utf-8') + MimeType(type='text', subtype='html', suffix='', + parameters={'charset': 'utf-8'}) + + """ + if not mimetype: + return MimeType( + type="", subtype="", suffix="", parameters=MultiDictProxy(MultiDict()) + ) + + parts = mimetype.split(";") + params: MultiDict[str] = MultiDict() + for item in parts[1:]: + if not item: + continue + key, value = cast( + Tuple[str, str], item.split("=", 1) if "=" in item else (item, "") + ) + params.add(key.lower().strip(), value.strip(' "')) + + fulltype = parts[0].strip().lower() + if fulltype == "*": + fulltype = "*/*" + + mtype, stype = ( + cast(Tuple[str, str], fulltype.split("/", 1)) + if "/" in fulltype + else (fulltype, "") + ) + stype, suffix = ( + cast(Tuple[str, str], stype.split("+", 1)) if "+" in stype else (stype, "") + ) + + return MimeType( + type=mtype, subtype=stype, suffix=suffix, parameters=MultiDictProxy(params) + ) + + +def guess_filename(obj: Any, default: Optional[str] = None) -> Optional[str]: + name = getattr(obj, "name", None) + if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">": + return Path(name).name + return default + + +not_qtext_re = re.compile(r"[^\041\043-\133\135-\176]") +QCONTENT = {chr(i) for i in range(0x20, 0x7F)} | {"\t"} + + +def quoted_string(content: str) -> str: + """Return 7-bit content as quoted-string. + + Format content into a quoted-string as defined in RFC5322 for + Internet Message Format. Notice that this is not the 8-bit HTTP + format, but the 7-bit email format. Content must be in usascii or + a ValueError is raised. + """ + if not (QCONTENT > set(content)): + raise ValueError(f"bad content for quoted-string {content!r}") + return not_qtext_re.sub(lambda x: "\\" + x.group(0), content) + + +def content_disposition_header( + disptype: str, quote_fields: bool = True, _charset: str = "utf-8", **params: str +) -> str: + """Sets ``Content-Disposition`` header for MIME. + + This is the MIME payload Content-Disposition header from RFC 2183 + and RFC 7579 section 4.2, not the HTTP Content-Disposition from + RFC 6266. + + disptype is a disposition type: inline, attachment, form-data. + Should be valid extension token (see RFC 2183) + + quote_fields performs value quoting to 7-bit MIME headers + according to RFC 7578. Set to quote_fields to False if recipient + can take 8-bit file names and field values. + + _charset specifies the charset to use when quote_fields is True. + + params is a dict with disposition params. + """ + if not disptype or not (TOKEN > set(disptype)): + raise ValueError("bad content disposition type {!r}" "".format(disptype)) + + value = disptype + if params: + lparams = [] + for key, val in params.items(): + if not key or not (TOKEN > set(key)): + raise ValueError( + "bad content disposition parameter" " {!r}={!r}".format(key, val) + ) + if quote_fields: + if key.lower() == "filename": + qval = quote(val, "", encoding=_charset) + lparams.append((key, '"%s"' % qval)) + else: + try: + qval = quoted_string(val) + except ValueError: + qval = "".join( + (_charset, "''", quote(val, "", encoding=_charset)) + ) + lparams.append((key + "*", qval)) + else: + lparams.append((key, '"%s"' % qval)) + else: + qval = val.replace("\\", "\\\\").replace('"', '\\"') + lparams.append((key, '"%s"' % qval)) + sparams = "; ".join("=".join(pair) for pair in lparams) + value = "; ".join((value, sparams)) + return value + + +class _TSelf(Protocol, Generic[_T]): + _cache: Dict[str, _T] + + +class reify(Generic[_T]): + """Use as a class method decorator. + + It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + """ + + def __init__(self, wrapped: Callable[..., _T]) -> None: + self.wrapped = wrapped + self.__doc__ = wrapped.__doc__ + self.name = wrapped.__name__ + + def __get__(self, inst: _TSelf[_T], owner: Optional[Type[Any]] = None) -> _T: + try: + try: + return inst._cache[self.name] + except KeyError: + val = self.wrapped(inst) + inst._cache[self.name] = val + return val + except AttributeError: + if inst is None: + return self + raise + + def __set__(self, inst: _TSelf[_T], value: _T) -> None: + raise AttributeError("reified property is read-only") + + +reify_py = reify + +try: + from ._helpers import reify as reify_c + + if not NO_EXTENSIONS: + reify = reify_c # type: ignore[misc,assignment] +except ImportError: + pass + +_ipv4_pattern = ( + r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}" + r"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" +) +_ipv6_pattern = ( + r"^(?:(?:(?:[A-F0-9]{1,4}:){6}|(?=(?:[A-F0-9]{0,4}:){0,6}" + r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}$)(([0-9A-F]{1,4}:){0,5}|:)" + r"((:[0-9A-F]{1,4}){1,5}:|:)|::(?:[A-F0-9]{1,4}:){5})" + r"(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}" + r"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])|(?:[A-F0-9]{1,4}:){7}" + r"[A-F0-9]{1,4}|(?=(?:[A-F0-9]{0,4}:){0,7}[A-F0-9]{0,4}$)" + r"(([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:)|(?:[A-F0-9]{1,4}:){7}" + r":|:(:[A-F0-9]{1,4}){7})$" +) +_ipv4_regex = re.compile(_ipv4_pattern) +_ipv6_regex = re.compile(_ipv6_pattern, flags=re.IGNORECASE) +_ipv4_regexb = re.compile(_ipv4_pattern.encode("ascii")) +_ipv6_regexb = re.compile(_ipv6_pattern.encode("ascii"), flags=re.IGNORECASE) + + +def _is_ip_address( + regex: Pattern[str], regexb: Pattern[bytes], host: Optional[Union[str, bytes]] +) -> bool: + if host is None: + return False + if isinstance(host, str): + return bool(regex.match(host)) + elif isinstance(host, (bytes, bytearray, memoryview)): + return bool(regexb.match(host)) + else: + raise TypeError(f"{host} [{type(host)}] is not a str or bytes") + + +is_ipv4_address = functools.partial(_is_ip_address, _ipv4_regex, _ipv4_regexb) +is_ipv6_address = functools.partial(_is_ip_address, _ipv6_regex, _ipv6_regexb) + + +def is_ip_address(host: Optional[Union[str, bytes, bytearray, memoryview]]) -> bool: + return is_ipv4_address(host) or is_ipv6_address(host) + + +def next_whole_second() -> datetime.datetime: + """Return current time rounded up to the next whole second.""" + return datetime.datetime.now(datetime.timezone.utc).replace( + microsecond=0 + ) + datetime.timedelta(seconds=0) + + +_cached_current_datetime: Optional[int] = None +_cached_formatted_datetime = "" + + +def rfc822_formatted_time() -> str: + global _cached_current_datetime + global _cached_formatted_datetime + + now = int(time.time()) + if now != _cached_current_datetime: + # Weekday and month names for HTTP date/time formatting; + # always English! + # Tuples are constants stored in codeobject! + _weekdayname = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") + _monthname = ( + "", # Dummy so we can use 1-based month numbers + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ) + + year, month, day, hh, mm, ss, wd, *tail = time.gmtime(now) + _cached_formatted_datetime = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( + _weekdayname[wd], + day, + _monthname[month], + year, + hh, + mm, + ss, + ) + _cached_current_datetime = now + return _cached_formatted_datetime + + +def _weakref_handle(info: "Tuple[weakref.ref[object], str]") -> None: + ref, name = info + ob = ref() + if ob is not None: + with suppress(Exception): + getattr(ob, name)() + + +def weakref_handle( + ob: object, name: str, timeout: float, loop: asyncio.AbstractEventLoop +) -> Optional[asyncio.TimerHandle]: + if timeout is not None and timeout > 0: + when = loop.time() + timeout + if timeout >= 5: + when = ceil(when) + + return loop.call_at(when, _weakref_handle, (weakref.ref(ob), name)) + return None + + +def call_later( + cb: Callable[[], Any], timeout: float, loop: asyncio.AbstractEventLoop +) -> Optional[asyncio.TimerHandle]: + if timeout is not None and timeout > 0: + when = loop.time() + timeout + if timeout > 5: + when = ceil(when) + return loop.call_at(when, cb) + return None + + +class TimeoutHandle: + """Timeout handle""" + + def __init__( + self, loop: asyncio.AbstractEventLoop, timeout: Optional[float] + ) -> None: + self._timeout = timeout + self._loop = loop + self._callbacks: List[ + Tuple[Callable[..., None], Tuple[Any, ...], Dict[str, Any]] + ] = [] + + def register( + self, callback: Callable[..., None], *args: Any, **kwargs: Any + ) -> None: + self._callbacks.append((callback, args, kwargs)) + + def close(self) -> None: + self._callbacks.clear() + + def start(self) -> Optional[asyncio.Handle]: + timeout = self._timeout + if timeout is not None and timeout > 0: + when = self._loop.time() + timeout + if timeout >= 5: + when = ceil(when) + return self._loop.call_at(when, self.__call__) + else: + return None + + def timer(self) -> "BaseTimerContext": + if self._timeout is not None and self._timeout > 0: + timer = TimerContext(self._loop) + self.register(timer.timeout) + return timer + else: + return TimerNoop() + + def __call__(self) -> None: + for cb, args, kwargs in self._callbacks: + with suppress(Exception): + cb(*args, **kwargs) + + self._callbacks.clear() + + +class BaseTimerContext(ContextManager["BaseTimerContext"]): + pass + + +class TimerNoop(BaseTimerContext): + def __enter__(self) -> BaseTimerContext: + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + return + + +class TimerContext(BaseTimerContext): + """Low resolution timeout context manager""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop = loop + self._tasks: List[asyncio.Task[Any]] = [] + self._cancelled = False + + def __enter__(self) -> BaseTimerContext: + task = current_task(loop=self._loop) + + if task is None: + raise RuntimeError( + "Timeout context manager should be used " "inside a task" + ) + + if self._cancelled: + raise asyncio.TimeoutError from None + + self._tasks.append(task) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + if self._tasks: + self._tasks.pop() + + if exc_type is asyncio.CancelledError and self._cancelled: + raise asyncio.TimeoutError from None + return None + + def timeout(self) -> None: + if not self._cancelled: + for task in set(self._tasks): + task.cancel() + + self._cancelled = True + + +def ceil_timeout(delay: Optional[float]) -> async_timeout.Timeout: + if delay is None or delay <= 0: + return async_timeout.timeout(None) + + loop = get_running_loop() + now = loop.time() + when = now + delay + if delay > 5: + when = ceil(when) + return async_timeout.timeout_at(when) + + +class HeadersMixin: + + ATTRS = frozenset(["_content_type", "_content_dict", "_stored_content_type"]) + + _content_type: Optional[str] = None + _content_dict: Optional[Dict[str, str]] = None + _stored_content_type = sentinel + + def _parse_content_type(self, raw: str) -> None: + self._stored_content_type = raw + if raw is None: + # default value according to RFC 2616 + self._content_type = "application/octet-stream" + self._content_dict = {} + else: + msg = HeaderParser().parsestr("Content-Type: " + raw) + self._content_type = msg.get_content_type() + params = msg.get_params() + self._content_dict = dict(params[1:]) # First element is content type again + + @property + def content_type(self) -> str: + """The value of content part for Content-Type HTTP header.""" + raw = self._headers.get(hdrs.CONTENT_TYPE) # type: ignore[attr-defined] + if self._stored_content_type != raw: + self._parse_content_type(raw) + return self._content_type # type: ignore[return-value] + + @property + def charset(self) -> Optional[str]: + """The value of charset part for Content-Type HTTP header.""" + raw = self._headers.get(hdrs.CONTENT_TYPE) # type: ignore[attr-defined] + if self._stored_content_type != raw: + self._parse_content_type(raw) + return self._content_dict.get("charset") # type: ignore[union-attr] + + @property + def content_length(self) -> Optional[int]: + """The value of Content-Length HTTP header.""" + content_length = self._headers.get( # type: ignore[attr-defined] + hdrs.CONTENT_LENGTH + ) + + if content_length is not None: + return int(content_length) + else: + return None + + +def set_result(fut: "asyncio.Future[_T]", result: _T) -> None: + if not fut.done(): + fut.set_result(result) + + +def set_exception(fut: "asyncio.Future[_T]", exc: BaseException) -> None: + if not fut.done(): + fut.set_exception(exc) + + +class ChainMapProxy(Mapping[str, Any]): + __slots__ = ("_maps",) + + def __init__(self, maps: Iterable[Mapping[str, Any]]) -> None: + self._maps = tuple(maps) + + def __init_subclass__(cls) -> None: + raise TypeError( + "Inheritance class {} from ChainMapProxy " + "is forbidden".format(cls.__name__) + ) + + def __getitem__(self, key: str) -> Any: + for mapping in self._maps: + try: + return mapping[key] + except KeyError: + pass + raise KeyError(key) + + def get(self, key: str, default: Any = None) -> Any: + return self[key] if key in self else default + + def __len__(self) -> int: + # reuses stored hash values if possible + return len(set().union(*self._maps)) # type: ignore[arg-type] + + def __iter__(self) -> Iterator[str]: + d: Dict[str, Any] = {} + for mapping in reversed(self._maps): + # reuses stored hash values if possible + d.update(mapping) + return iter(d) + + def __contains__(self, key: object) -> bool: + return any(key in m for m in self._maps) + + def __bool__(self) -> bool: + return any(self._maps) + + def __repr__(self) -> str: + content = ", ".join(map(repr, self._maps)) + return f"ChainMapProxy({content})" + + +# https://tools.ietf.org/html/rfc7232#section-2.3 +_ETAGC = r"[!#-}\x80-\xff]+" +_ETAGC_RE = re.compile(_ETAGC) +_QUOTED_ETAG = rf'(W/)?"({_ETAGC})"' +QUOTED_ETAG_RE = re.compile(_QUOTED_ETAG) +LIST_QUOTED_ETAG_RE = re.compile(rf"({_QUOTED_ETAG})(?:\s*,\s*|$)|(.)") + +ETAG_ANY = "*" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class ETag: + value: str + is_weak: bool = False + + +def validate_etag_value(value: str) -> None: + if value != ETAG_ANY and not _ETAGC_RE.fullmatch(value): + raise ValueError( + f"Value {value!r} is not a valid etag. Maybe it contains '\"'?" + ) + + +def parse_http_date(date_str: Optional[str]) -> Optional[datetime.datetime]: + """Process a date string, return a datetime object""" + if date_str is not None: + timetuple = parsedate(date_str) + if timetuple is not None: + with suppress(ValueError): + return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc) + return None diff --git a/lib/aiohttp/http.py b/lib/aiohttp/http.py new file mode 100644 index 0000000..ca9dc54 --- /dev/null +++ b/lib/aiohttp/http.py @@ -0,0 +1,70 @@ +import http.server +import sys +from typing import Mapping, Tuple + +from . import __version__ +from .http_exceptions import HttpProcessingError as HttpProcessingError +from .http_parser import ( + HeadersParser as HeadersParser, + HttpParser as HttpParser, + HttpRequestParser as HttpRequestParser, + HttpResponseParser as HttpResponseParser, + RawRequestMessage as RawRequestMessage, + RawResponseMessage as RawResponseMessage, +) +from .http_websocket import ( + WS_CLOSED_MESSAGE as WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE as WS_CLOSING_MESSAGE, + WS_KEY as WS_KEY, + WebSocketError as WebSocketError, + WebSocketReader as WebSocketReader, + WebSocketWriter as WebSocketWriter, + WSCloseCode as WSCloseCode, + WSMessage as WSMessage, + WSMsgType as WSMsgType, + ws_ext_gen as ws_ext_gen, + ws_ext_parse as ws_ext_parse, +) +from .http_writer import ( + HttpVersion as HttpVersion, + HttpVersion10 as HttpVersion10, + HttpVersion11 as HttpVersion11, + StreamWriter as StreamWriter, +) + +__all__ = ( + "HttpProcessingError", + "RESPONSES", + "SERVER_SOFTWARE", + # .http_writer + "StreamWriter", + "HttpVersion", + "HttpVersion10", + "HttpVersion11", + # .http_parser + "HeadersParser", + "HttpParser", + "HttpRequestParser", + "HttpResponseParser", + "RawRequestMessage", + "RawResponseMessage", + # .http_websocket + "WS_CLOSED_MESSAGE", + "WS_CLOSING_MESSAGE", + "WS_KEY", + "WebSocketReader", + "WebSocketWriter", + "ws_ext_gen", + "ws_ext_parse", + "WSMessage", + "WebSocketError", + "WSMsgType", + "WSCloseCode", +) + + +SERVER_SOFTWARE: str = "Python/{0[0]}.{0[1]} aiohttp/{1}".format( + sys.version_info, __version__ +) + +RESPONSES: Mapping[int, Tuple[str, str]] = http.server.BaseHTTPRequestHandler.responses diff --git a/lib/aiohttp/http_exceptions.py b/lib/aiohttp/http_exceptions.py new file mode 100644 index 0000000..c885f80 --- /dev/null +++ b/lib/aiohttp/http_exceptions.py @@ -0,0 +1,105 @@ +"""Low-level http related exceptions.""" + + +from typing import Optional, Union + +from .typedefs import _CIMultiDict + +__all__ = ("HttpProcessingError",) + + +class HttpProcessingError(Exception): + """HTTP error. + + Shortcut for raising HTTP errors with custom code, message and headers. + + code: HTTP Error code. + message: (optional) Error message. + headers: (optional) Headers to be sent in response, a list of pairs + """ + + code = 0 + message = "" + headers = None + + def __init__( + self, + *, + code: Optional[int] = None, + message: str = "", + headers: Optional[_CIMultiDict] = None, + ) -> None: + if code is not None: + self.code = code + self.headers = headers + self.message = message + + def __str__(self) -> str: + return f"{self.code}, message={self.message!r}" + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: {self}>" + + +class BadHttpMessage(HttpProcessingError): + + code = 400 + message = "Bad Request" + + def __init__(self, message: str, *, headers: Optional[_CIMultiDict] = None) -> None: + super().__init__(message=message, headers=headers) + self.args = (message,) + + +class HttpBadRequest(BadHttpMessage): + + code = 400 + message = "Bad Request" + + +class PayloadEncodingError(BadHttpMessage): + """Base class for payload errors""" + + +class ContentEncodingError(PayloadEncodingError): + """Content encoding error.""" + + +class TransferEncodingError(PayloadEncodingError): + """transfer encoding error.""" + + +class ContentLengthError(PayloadEncodingError): + """Not enough data for satisfy content length header.""" + + +class LineTooLong(BadHttpMessage): + def __init__( + self, line: str, limit: str = "Unknown", actual_size: str = "Unknown" + ) -> None: + super().__init__( + f"Got more than {limit} bytes ({actual_size}) when reading {line}." + ) + self.args = (line, limit, actual_size) + + +class InvalidHeader(BadHttpMessage): + def __init__(self, hdr: Union[bytes, str]) -> None: + if isinstance(hdr, bytes): + hdr = hdr.decode("utf-8", "surrogateescape") + super().__init__(f"Invalid HTTP Header: {hdr}") + self.hdr = hdr + self.args = (hdr,) + + +class BadStatusLine(BadHttpMessage): + def __init__(self, line: str = "") -> None: + if not isinstance(line, str): + line = repr(line) + super().__init__(f"Bad status line {line!r}") + self.args = (line,) + self.line = line + + +class InvalidURLError(BadHttpMessage): + pass diff --git a/lib/aiohttp/http_parser.py b/lib/aiohttp/http_parser.py new file mode 100644 index 0000000..5a66ce4 --- /dev/null +++ b/lib/aiohttp/http_parser.py @@ -0,0 +1,969 @@ +import abc +import asyncio +import collections +import re +import string +import zlib +from contextlib import suppress +from enum import IntEnum +from typing import ( + Any, + Generic, + List, + NamedTuple, + Optional, + Pattern, + Set, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +from multidict import CIMultiDict, CIMultiDictProxy, istr +from yarl import URL + +from . import hdrs +from .base_protocol import BaseProtocol +from .helpers import NO_EXTENSIONS, BaseTimerContext +from .http_exceptions import ( + BadHttpMessage, + BadStatusLine, + ContentEncodingError, + ContentLengthError, + InvalidHeader, + LineTooLong, + TransferEncodingError, +) +from .http_writer import HttpVersion, HttpVersion10 +from .log import internal_logger +from .streams import EMPTY_PAYLOAD, StreamReader +from .typedefs import Final, RawHeaders + +try: + import brotli + + HAS_BROTLI = True +except ImportError: # pragma: no cover + HAS_BROTLI = False + + +__all__ = ( + "HeadersParser", + "HttpParser", + "HttpRequestParser", + "HttpResponseParser", + "RawRequestMessage", + "RawResponseMessage", +) + +ASCIISET: Final[Set[str]] = set(string.printable) + +# See https://tools.ietf.org/html/rfc7230#section-3.1.1 +# and https://tools.ietf.org/html/rfc7230#appendix-B +# +# method = token +# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / +# "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +# token = 1*tchar +METHRE: Final[Pattern[str]] = re.compile(r"[!#$%&'*+\-.^_`|~0-9A-Za-z]+") +VERSRE: Final[Pattern[str]] = re.compile(r"HTTP/(\d+).(\d+)") +HDRRE: Final[Pattern[bytes]] = re.compile(rb"[\x00-\x1F\x7F()<>@,;:\[\]={} \t\\\\\"]") + + +class RawRequestMessage(NamedTuple): + method: str + path: str + version: HttpVersion + headers: "CIMultiDictProxy[str]" + raw_headers: RawHeaders + should_close: bool + compression: Optional[str] + upgrade: bool + chunked: bool + url: URL + + +RawResponseMessage = collections.namedtuple( + "RawResponseMessage", + [ + "version", + "code", + "reason", + "headers", + "raw_headers", + "should_close", + "compression", + "upgrade", + "chunked", + ], +) + + +_MsgT = TypeVar("_MsgT", RawRequestMessage, RawResponseMessage) + + +class ParseState(IntEnum): + + PARSE_NONE = 0 + PARSE_LENGTH = 1 + PARSE_CHUNKED = 2 + PARSE_UNTIL_EOF = 3 + + +class ChunkState(IntEnum): + PARSE_CHUNKED_SIZE = 0 + PARSE_CHUNKED_CHUNK = 1 + PARSE_CHUNKED_CHUNK_EOF = 2 + PARSE_MAYBE_TRAILERS = 3 + PARSE_TRAILERS = 4 + + +class HeadersParser: + def __init__( + self, + max_line_size: int = 8190, + max_headers: int = 32768, + max_field_size: int = 8190, + ) -> None: + self.max_line_size = max_line_size + self.max_headers = max_headers + self.max_field_size = max_field_size + + def parse_headers( + self, lines: List[bytes] + ) -> Tuple["CIMultiDictProxy[str]", RawHeaders]: + headers: CIMultiDict[str] = CIMultiDict() + raw_headers = [] + + lines_idx = 1 + line = lines[1] + line_count = len(lines) + + while line: + # Parse initial header name : value pair. + try: + bname, bvalue = line.split(b":", 1) + except ValueError: + raise InvalidHeader(line) from None + + bname = bname.strip(b" \t") + bvalue = bvalue.lstrip() + if HDRRE.search(bname): + raise InvalidHeader(bname) + if len(bname) > self.max_field_size: + raise LineTooLong( + "request header name {}".format( + bname.decode("utf8", "xmlcharrefreplace") + ), + str(self.max_field_size), + str(len(bname)), + ) + + header_length = len(bvalue) + + # next line + lines_idx += 1 + line = lines[lines_idx] + + # consume continuation lines + continuation = line and line[0] in (32, 9) # (' ', '\t') + + if continuation: + bvalue_lst = [bvalue] + while continuation: + header_length += len(line) + if header_length > self.max_field_size: + raise LineTooLong( + "request header field {}".format( + bname.decode("utf8", "xmlcharrefreplace") + ), + str(self.max_field_size), + str(header_length), + ) + bvalue_lst.append(line) + + # next line + lines_idx += 1 + if lines_idx < line_count: + line = lines[lines_idx] + if line: + continuation = line[0] in (32, 9) # (' ', '\t') + else: + line = b"" + break + bvalue = b"".join(bvalue_lst) + else: + if header_length > self.max_field_size: + raise LineTooLong( + "request header field {}".format( + bname.decode("utf8", "xmlcharrefreplace") + ), + str(self.max_field_size), + str(header_length), + ) + + bvalue = bvalue.strip() + name = bname.decode("utf-8", "surrogateescape") + value = bvalue.decode("utf-8", "surrogateescape") + + headers.add(name, value) + raw_headers.append((bname, bvalue)) + + return (CIMultiDictProxy(headers), tuple(raw_headers)) + + +class HttpParser(abc.ABC, Generic[_MsgT]): + def __init__( + self, + protocol: Optional[BaseProtocol] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + limit: int = 2**16, + max_line_size: int = 8190, + max_headers: int = 32768, + max_field_size: int = 8190, + timer: Optional[BaseTimerContext] = None, + code: Optional[int] = None, + method: Optional[str] = None, + readall: bool = False, + payload_exception: Optional[Type[BaseException]] = None, + response_with_body: bool = True, + read_until_eof: bool = False, + auto_decompress: bool = True, + ) -> None: + self.protocol = protocol + self.loop = loop + self.max_line_size = max_line_size + self.max_headers = max_headers + self.max_field_size = max_field_size + self.timer = timer + self.code = code + self.method = method + self.readall = readall + self.payload_exception = payload_exception + self.response_with_body = response_with_body + self.read_until_eof = read_until_eof + + self._lines: List[bytes] = [] + self._tail = b"" + self._upgraded = False + self._payload = None + self._payload_parser: Optional[HttpPayloadParser] = None + self._auto_decompress = auto_decompress + self._limit = limit + self._headers_parser = HeadersParser(max_line_size, max_headers, max_field_size) + + @abc.abstractmethod + def parse_message(self, lines: List[bytes]) -> _MsgT: + pass + + def feed_eof(self) -> Optional[_MsgT]: + if self._payload_parser is not None: + self._payload_parser.feed_eof() + self._payload_parser = None + else: + # try to extract partial message + if self._tail: + self._lines.append(self._tail) + + if self._lines: + if self._lines[-1] != "\r\n": + self._lines.append(b"") + with suppress(Exception): + return self.parse_message(self._lines) + return None + + def feed_data( + self, + data: bytes, + SEP: bytes = b"\r\n", + EMPTY: bytes = b"", + CONTENT_LENGTH: istr = hdrs.CONTENT_LENGTH, + METH_CONNECT: str = hdrs.METH_CONNECT, + SEC_WEBSOCKET_KEY1: istr = hdrs.SEC_WEBSOCKET_KEY1, + ) -> Tuple[List[Tuple[_MsgT, StreamReader]], bool, bytes]: + + messages = [] + + if self._tail: + data, self._tail = self._tail + data, b"" + + data_len = len(data) + start_pos = 0 + loop = self.loop + + while start_pos < data_len: + + # read HTTP message (request/response line + headers), \r\n\r\n + # and split by lines + if self._payload_parser is None and not self._upgraded: + pos = data.find(SEP, start_pos) + # consume \r\n + if pos == start_pos and not self._lines: + start_pos = pos + 2 + continue + + if pos >= start_pos: + # line found + self._lines.append(data[start_pos:pos]) + start_pos = pos + 2 + + # \r\n\r\n found + if self._lines[-1] == EMPTY: + try: + msg: _MsgT = self.parse_message(self._lines) + finally: + self._lines.clear() + + def get_content_length() -> Optional[int]: + # payload length + length_hdr = msg.headers.get(CONTENT_LENGTH) + if length_hdr is None: + return None + + try: + length = int(length_hdr) + except ValueError: + raise InvalidHeader(CONTENT_LENGTH) + + if length < 0: + raise InvalidHeader(CONTENT_LENGTH) + + return length + + length = get_content_length() + # do not support old websocket spec + if SEC_WEBSOCKET_KEY1 in msg.headers: + raise InvalidHeader(SEC_WEBSOCKET_KEY1) + + self._upgraded = msg.upgrade + + method = getattr(msg, "method", self.method) + + assert self.protocol is not None + # calculate payload + if ( + (length is not None and length > 0) + or msg.chunked + and not msg.upgrade + ): + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + payload_parser = HttpPayloadParser( + payload, + length=length, + chunked=msg.chunked, + method=method, + compression=msg.compression, + code=self.code, + readall=self.readall, + response_with_body=self.response_with_body, + auto_decompress=self._auto_decompress, + ) + if not payload_parser.done: + self._payload_parser = payload_parser + elif method == METH_CONNECT: + assert isinstance(msg, RawRequestMessage) + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + self._upgraded = True + self._payload_parser = HttpPayloadParser( + payload, + method=msg.method, + compression=msg.compression, + readall=True, + auto_decompress=self._auto_decompress, + ) + else: + if ( + getattr(msg, "code", 100) >= 199 + and length is None + and self.read_until_eof + ): + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + payload_parser = HttpPayloadParser( + payload, + length=length, + chunked=msg.chunked, + method=method, + compression=msg.compression, + code=self.code, + readall=True, + response_with_body=self.response_with_body, + auto_decompress=self._auto_decompress, + ) + if not payload_parser.done: + self._payload_parser = payload_parser + else: + payload = EMPTY_PAYLOAD + + messages.append((msg, payload)) + else: + self._tail = data[start_pos:] + data = EMPTY + break + + # no parser, just store + elif self._payload_parser is None and self._upgraded: + assert not self._lines + break + + # feed payload + elif data and start_pos < data_len: + assert not self._lines + assert self._payload_parser is not None + try: + eof, data = self._payload_parser.feed_data(data[start_pos:]) + except BaseException as exc: + if self.payload_exception is not None: + self._payload_parser.payload.set_exception( + self.payload_exception(str(exc)) + ) + else: + self._payload_parser.payload.set_exception(exc) + + eof = True + data = b"" + + if eof: + start_pos = 0 + data_len = len(data) + self._payload_parser = None + continue + else: + break + + if data and start_pos < data_len: + data = data[start_pos:] + else: + data = EMPTY + + return messages, self._upgraded, data + + def parse_headers( + self, lines: List[bytes] + ) -> Tuple[ + "CIMultiDictProxy[str]", RawHeaders, Optional[bool], Optional[str], bool, bool + ]: + """Parses RFC 5322 headers from a stream. + + Line continuations are supported. Returns list of header name + and value pairs. Header name is in upper case. + """ + headers, raw_headers = self._headers_parser.parse_headers(lines) + close_conn = None + encoding = None + upgrade = False + chunked = False + + # keep-alive + conn = headers.get(hdrs.CONNECTION) + if conn: + v = conn.lower() + if v == "close": + close_conn = True + elif v == "keep-alive": + close_conn = False + elif v == "upgrade": + upgrade = True + + # encoding + enc = headers.get(hdrs.CONTENT_ENCODING) + if enc: + enc = enc.lower() + if enc in ("gzip", "deflate", "br"): + encoding = enc + + # chunking + te = headers.get(hdrs.TRANSFER_ENCODING) + if te is not None: + if "chunked" == te.lower(): + chunked = True + else: + raise BadHttpMessage("Request has invalid `Transfer-Encoding`") + + if hdrs.CONTENT_LENGTH in headers: + raise BadHttpMessage( + "Content-Length can't be present with Transfer-Encoding", + ) + + return (headers, raw_headers, close_conn, encoding, upgrade, chunked) + + def set_upgraded(self, val: bool) -> None: + """Set connection upgraded (to websocket) mode. + + :param bool val: new state. + """ + self._upgraded = val + + +class HttpRequestParser(HttpParser[RawRequestMessage]): + """Read request status line. + + Exception .http_exceptions.BadStatusLine + could be raised in case of any errors in status line. + Returns RawRequestMessage. + """ + + def parse_message(self, lines: List[bytes]) -> RawRequestMessage: + # request line + line = lines[0].decode("utf-8", "surrogateescape") + try: + method, path, version = line.split(None, 2) + except ValueError: + raise BadStatusLine(line) from None + + if len(path) > self.max_line_size: + raise LineTooLong( + "Status line is too long", str(self.max_line_size), str(len(path)) + ) + + # method + if not METHRE.match(method): + raise BadStatusLine(method) + + # version + try: + if version.startswith("HTTP/"): + n1, n2 = version[5:].split(".", 1) + version_o = HttpVersion(int(n1), int(n2)) + else: + raise BadStatusLine(version) + except Exception: + raise BadStatusLine(version) + + if method == "CONNECT": + # authority-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3 + url = URL.build(authority=path, encoded=True) + elif path.startswith("/"): + # origin-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1 + path_part, _hash_separator, url_fragment = path.partition("#") + path_part, _question_mark_separator, qs_part = path_part.partition("?") + + # NOTE: `yarl.URL.build()` is used to mimic what the Cython-based + # NOTE: parser does, otherwise it results into the same + # NOTE: HTTP Request-Line input producing different + # NOTE: `yarl.URL()` objects + url = URL.build( + path=path_part, + query_string=qs_part, + fragment=url_fragment, + encoded=True, + ) + else: + # absolute-form for proxy maybe, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2 + url = URL(path, encoded=True) + + # read headers + ( + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) = self.parse_headers(lines) + + if close is None: # then the headers weren't set in the request + if version_o <= HttpVersion10: # HTTP 1.0 must asks to not close + close = True + else: # HTTP 1.1 must ask to close. + close = False + + return RawRequestMessage( + method, + path, + version_o, + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + url, + ) + + +class HttpResponseParser(HttpParser[RawResponseMessage]): + """Read response status line and headers. + + BadStatusLine could be raised in case of any errors in status line. + Returns RawResponseMessage. + """ + + def parse_message(self, lines: List[bytes]) -> RawResponseMessage: + line = lines[0].decode("utf-8", "surrogateescape") + try: + version, status = line.split(None, 1) + except ValueError: + raise BadStatusLine(line) from None + + try: + status, reason = status.split(None, 1) + except ValueError: + reason = "" + + if len(reason) > self.max_line_size: + raise LineTooLong( + "Status line is too long", str(self.max_line_size), str(len(reason)) + ) + + # version + match = VERSRE.match(version) + if match is None: + raise BadStatusLine(line) + version_o = HttpVersion(int(match.group(1)), int(match.group(2))) + + # The status code is a three-digit number + try: + status_i = int(status) + except ValueError: + raise BadStatusLine(line) from None + + if status_i > 999: + raise BadStatusLine(line) + + # read headers + ( + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) = self.parse_headers(lines) + + if close is None: + close = version_o <= HttpVersion10 + + return RawResponseMessage( + version_o, + status_i, + reason.strip(), + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) + + +class HttpPayloadParser: + def __init__( + self, + payload: StreamReader, + length: Optional[int] = None, + chunked: bool = False, + compression: Optional[str] = None, + code: Optional[int] = None, + method: Optional[str] = None, + readall: bool = False, + response_with_body: bool = True, + auto_decompress: bool = True, + ) -> None: + self._length = 0 + self._type = ParseState.PARSE_NONE + self._chunk = ChunkState.PARSE_CHUNKED_SIZE + self._chunk_size = 0 + self._chunk_tail = b"" + self._auto_decompress = auto_decompress + self.done = False + + # payload decompression wrapper + if response_with_body and compression and self._auto_decompress: + real_payload: Union[StreamReader, DeflateBuffer] = DeflateBuffer( + payload, compression + ) + else: + real_payload = payload + + # payload parser + if not response_with_body: + # don't parse payload if it's not expected to be received + self._type = ParseState.PARSE_NONE + real_payload.feed_eof() + self.done = True + + elif chunked: + self._type = ParseState.PARSE_CHUNKED + elif length is not None: + self._type = ParseState.PARSE_LENGTH + self._length = length + if self._length == 0: + real_payload.feed_eof() + self.done = True + else: + if readall and code != 204: + self._type = ParseState.PARSE_UNTIL_EOF + elif method in ("PUT", "POST"): + internal_logger.warning( # pragma: no cover + "Content-Length or Transfer-Encoding header is required" + ) + self._type = ParseState.PARSE_NONE + real_payload.feed_eof() + self.done = True + + self.payload = real_payload + + def feed_eof(self) -> None: + if self._type == ParseState.PARSE_UNTIL_EOF: + self.payload.feed_eof() + elif self._type == ParseState.PARSE_LENGTH: + raise ContentLengthError( + "Not enough data for satisfy content length header." + ) + elif self._type == ParseState.PARSE_CHUNKED: + raise TransferEncodingError( + "Not enough data for satisfy transfer length header." + ) + + def feed_data( + self, chunk: bytes, SEP: bytes = b"\r\n", CHUNK_EXT: bytes = b";" + ) -> Tuple[bool, bytes]: + # Read specified amount of bytes + if self._type == ParseState.PARSE_LENGTH: + required = self._length + chunk_len = len(chunk) + + if required >= chunk_len: + self._length = required - chunk_len + self.payload.feed_data(chunk, chunk_len) + if self._length == 0: + self.payload.feed_eof() + return True, b"" + else: + self._length = 0 + self.payload.feed_data(chunk[:required], required) + self.payload.feed_eof() + return True, chunk[required:] + + # Chunked transfer encoding parser + elif self._type == ParseState.PARSE_CHUNKED: + if self._chunk_tail: + chunk = self._chunk_tail + chunk + self._chunk_tail = b"" + + while chunk: + + # read next chunk size + if self._chunk == ChunkState.PARSE_CHUNKED_SIZE: + pos = chunk.find(SEP) + if pos >= 0: + i = chunk.find(CHUNK_EXT, 0, pos) + if i >= 0: + size_b = chunk[:i] # strip chunk-extensions + else: + size_b = chunk[:pos] + + try: + size = int(bytes(size_b), 16) + except ValueError: + exc = TransferEncodingError( + chunk[:pos].decode("ascii", "surrogateescape") + ) + self.payload.set_exception(exc) + raise exc from None + + chunk = chunk[pos + 2 :] + if size == 0: # eof marker + self._chunk = ChunkState.PARSE_MAYBE_TRAILERS + else: + self._chunk = ChunkState.PARSE_CHUNKED_CHUNK + self._chunk_size = size + self.payload.begin_http_chunk_receiving() + else: + self._chunk_tail = chunk + return False, b"" + + # read chunk and feed buffer + if self._chunk == ChunkState.PARSE_CHUNKED_CHUNK: + required = self._chunk_size + chunk_len = len(chunk) + + if required > chunk_len: + self._chunk_size = required - chunk_len + self.payload.feed_data(chunk, chunk_len) + return False, b"" + else: + self._chunk_size = 0 + self.payload.feed_data(chunk[:required], required) + chunk = chunk[required:] + self._chunk = ChunkState.PARSE_CHUNKED_CHUNK_EOF + self.payload.end_http_chunk_receiving() + + # toss the CRLF at the end of the chunk + if self._chunk == ChunkState.PARSE_CHUNKED_CHUNK_EOF: + if chunk[:2] == SEP: + chunk = chunk[2:] + self._chunk = ChunkState.PARSE_CHUNKED_SIZE + else: + self._chunk_tail = chunk + return False, b"" + + # if stream does not contain trailer, after 0\r\n + # we should get another \r\n otherwise + # trailers needs to be skiped until \r\n\r\n + if self._chunk == ChunkState.PARSE_MAYBE_TRAILERS: + head = chunk[:2] + if head == SEP: + # end of stream + self.payload.feed_eof() + return True, chunk[2:] + # Both CR and LF, or only LF may not be received yet. It is + # expected that CRLF or LF will be shown at the very first + # byte next time, otherwise trailers should come. The last + # CRLF which marks the end of response might not be + # contained in the same TCP segment which delivered the + # size indicator. + if not head: + return False, b"" + if head == SEP[:1]: + self._chunk_tail = head + return False, b"" + self._chunk = ChunkState.PARSE_TRAILERS + + # read and discard trailer up to the CRLF terminator + if self._chunk == ChunkState.PARSE_TRAILERS: + pos = chunk.find(SEP) + if pos >= 0: + chunk = chunk[pos + 2 :] + self._chunk = ChunkState.PARSE_MAYBE_TRAILERS + else: + self._chunk_tail = chunk + return False, b"" + + # Read all bytes until eof + elif self._type == ParseState.PARSE_UNTIL_EOF: + self.payload.feed_data(chunk, len(chunk)) + + return False, b"" + + +class DeflateBuffer: + """DeflateStream decompress stream and feed data into specified stream.""" + + decompressor: Any + + def __init__(self, out: StreamReader, encoding: Optional[str]) -> None: + self.out = out + self.size = 0 + self.encoding = encoding + self._started_decoding = False + + if encoding == "br": + if not HAS_BROTLI: # pragma: no cover + raise ContentEncodingError( + "Can not decode content-encoding: brotli (br). " + "Please install `Brotli`" + ) + + class BrotliDecoder: + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self) -> None: + self._obj = brotli.Decompressor() + + def decompress(self, data: bytes) -> bytes: + if hasattr(self._obj, "decompress"): + return cast(bytes, self._obj.decompress(data)) + return cast(bytes, self._obj.process(data)) + + def flush(self) -> bytes: + if hasattr(self._obj, "flush"): + return cast(bytes, self._obj.flush()) + return b"" + + self.decompressor = BrotliDecoder() + else: + zlib_mode = 16 + zlib.MAX_WBITS if encoding == "gzip" else zlib.MAX_WBITS + self.decompressor = zlib.decompressobj(wbits=zlib_mode) + + def set_exception(self, exc: BaseException) -> None: + self.out.set_exception(exc) + + def feed_data(self, chunk: bytes, size: int) -> None: + if not size: + return + + self.size += size + + # RFC1950 + # bits 0..3 = CM = 0b1000 = 8 = "deflate" + # bits 4..7 = CINFO = 1..7 = windows size. + if ( + not self._started_decoding + and self.encoding == "deflate" + and chunk[0] & 0xF != 8 + ): + # Change the decoder to decompress incorrectly compressed data + # Actually we should issue a warning about non-RFC-compliant data. + self.decompressor = zlib.decompressobj(wbits=-zlib.MAX_WBITS) + + try: + chunk = self.decompressor.decompress(chunk) + except Exception: + raise ContentEncodingError( + "Can not decode content-encoding: %s" % self.encoding + ) + + self._started_decoding = True + + if chunk: + self.out.feed_data(chunk, len(chunk)) + + def feed_eof(self) -> None: + chunk = self.decompressor.flush() + + if chunk or self.size > 0: + self.out.feed_data(chunk, len(chunk)) + if self.encoding == "deflate" and not self.decompressor.eof: + raise ContentEncodingError("deflate") + + self.out.feed_eof() + + def begin_http_chunk_receiving(self) -> None: + self.out.begin_http_chunk_receiving() + + def end_http_chunk_receiving(self) -> None: + self.out.end_http_chunk_receiving() + + +HttpRequestParserPy = HttpRequestParser +HttpResponseParserPy = HttpResponseParser +RawRequestMessagePy = RawRequestMessage +RawResponseMessagePy = RawResponseMessage + +try: + if not NO_EXTENSIONS: + from ._http_parser import ( # type: ignore[import,no-redef] + HttpRequestParser, + HttpResponseParser, + RawRequestMessage, + RawResponseMessage, + ) + + HttpRequestParserC = HttpRequestParser + HttpResponseParserC = HttpResponseParser + RawRequestMessageC = RawRequestMessage + RawResponseMessageC = RawResponseMessage +except ImportError: # pragma: no cover + pass diff --git a/lib/aiohttp/http_websocket.py b/lib/aiohttp/http_websocket.py new file mode 100644 index 0000000..2cfc519 --- /dev/null +++ b/lib/aiohttp/http_websocket.py @@ -0,0 +1,701 @@ +"""WebSocket protocol versions 13 and 8.""" + +import asyncio +import collections +import json +import random +import re +import sys +import zlib +from enum import IntEnum +from struct import Struct +from typing import Any, Callable, List, Optional, Pattern, Set, Tuple, Union, cast + +from .base_protocol import BaseProtocol +from .helpers import NO_EXTENSIONS +from .streams import DataQueue +from .typedefs import Final + +__all__ = ( + "WS_CLOSED_MESSAGE", + "WS_CLOSING_MESSAGE", + "WS_KEY", + "WebSocketReader", + "WebSocketWriter", + "WSMessage", + "WebSocketError", + "WSMsgType", + "WSCloseCode", +) + + +class WSCloseCode(IntEnum): + OK = 1000 + GOING_AWAY = 1001 + PROTOCOL_ERROR = 1002 + UNSUPPORTED_DATA = 1003 + ABNORMAL_CLOSURE = 1006 + INVALID_TEXT = 1007 + POLICY_VIOLATION = 1008 + MESSAGE_TOO_BIG = 1009 + MANDATORY_EXTENSION = 1010 + INTERNAL_ERROR = 1011 + SERVICE_RESTART = 1012 + TRY_AGAIN_LATER = 1013 + BAD_GATEWAY = 1014 + + +ALLOWED_CLOSE_CODES: Final[Set[int]] = {int(i) for i in WSCloseCode} + + +class WSMsgType(IntEnum): + # websocket spec types + CONTINUATION = 0x0 + TEXT = 0x1 + BINARY = 0x2 + PING = 0x9 + PONG = 0xA + CLOSE = 0x8 + + # aiohttp specific types + CLOSING = 0x100 + CLOSED = 0x101 + ERROR = 0x102 + + text = TEXT + binary = BINARY + ping = PING + pong = PONG + close = CLOSE + closing = CLOSING + closed = CLOSED + error = ERROR + + +WS_KEY: Final[bytes] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + +UNPACK_LEN2 = Struct("!H").unpack_from +UNPACK_LEN3 = Struct("!Q").unpack_from +UNPACK_CLOSE_CODE = Struct("!H").unpack +PACK_LEN1 = Struct("!BB").pack +PACK_LEN2 = Struct("!BBH").pack +PACK_LEN3 = Struct("!BBQ").pack +PACK_CLOSE_CODE = Struct("!H").pack +MSG_SIZE: Final[int] = 2**14 +DEFAULT_LIMIT: Final[int] = 2**16 + + +_WSMessageBase = collections.namedtuple("_WSMessageBase", ["type", "data", "extra"]) + + +class WSMessage(_WSMessageBase): + def json(self, *, loads: Callable[[Any], Any] = json.loads) -> Any: + """Return parsed JSON data. + + .. versionadded:: 0.22 + """ + return loads(self.data) + + +WS_CLOSED_MESSAGE = WSMessage(WSMsgType.CLOSED, None, None) +WS_CLOSING_MESSAGE = WSMessage(WSMsgType.CLOSING, None, None) + + +class WebSocketError(Exception): + """WebSocket protocol parser error.""" + + def __init__(self, code: int, message: str) -> None: + self.code = code + super().__init__(code, message) + + def __str__(self) -> str: + return cast(str, self.args[1]) + + +class WSHandshakeError(Exception): + """WebSocket protocol handshake error.""" + + +native_byteorder: Final[str] = sys.byteorder + + +# Used by _websocket_mask_python +_XOR_TABLE: Final[List[bytes]] = [bytes(a ^ b for a in range(256)) for b in range(256)] + + +def _websocket_mask_python(mask: bytes, data: bytearray) -> None: + """Websocket masking function. + + `mask` is a `bytes` object of length 4; `data` is a `bytearray` + object of any length. The contents of `data` are masked with `mask`, + as specified in section 5.3 of RFC 6455. + + Note that this function mutates the `data` argument. + + This pure-python implementation may be replaced by an optimized + version when available. + + """ + assert isinstance(data, bytearray), data + assert len(mask) == 4, mask + + if data: + a, b, c, d = (_XOR_TABLE[n] for n in mask) + data[::4] = data[::4].translate(a) + data[1::4] = data[1::4].translate(b) + data[2::4] = data[2::4].translate(c) + data[3::4] = data[3::4].translate(d) + + +if NO_EXTENSIONS: # pragma: no cover + _websocket_mask = _websocket_mask_python +else: + try: + from ._websocket import _websocket_mask_cython # type: ignore[import] + + _websocket_mask = _websocket_mask_cython + except ImportError: # pragma: no cover + _websocket_mask = _websocket_mask_python + +_WS_DEFLATE_TRAILING: Final[bytes] = bytes([0x00, 0x00, 0xFF, 0xFF]) + + +_WS_EXT_RE: Final[Pattern[str]] = re.compile( + r"^(?:;\s*(?:" + r"(server_no_context_takeover)|" + r"(client_no_context_takeover)|" + r"(server_max_window_bits(?:=(\d+))?)|" + r"(client_max_window_bits(?:=(\d+))?)))*$" +) + +_WS_EXT_RE_SPLIT: Final[Pattern[str]] = re.compile(r"permessage-deflate([^,]+)?") + + +def ws_ext_parse(extstr: Optional[str], isserver: bool = False) -> Tuple[int, bool]: + if not extstr: + return 0, False + + compress = 0 + notakeover = False + for ext in _WS_EXT_RE_SPLIT.finditer(extstr): + defext = ext.group(1) + # Return compress = 15 when get `permessage-deflate` + if not defext: + compress = 15 + break + match = _WS_EXT_RE.match(defext) + if match: + compress = 15 + if isserver: + # Server never fail to detect compress handshake. + # Server does not need to send max wbit to client + if match.group(4): + compress = int(match.group(4)) + # Group3 must match if group4 matches + # Compress wbit 8 does not support in zlib + # If compress level not support, + # CONTINUE to next extension + if compress > 15 or compress < 9: + compress = 0 + continue + if match.group(1): + notakeover = True + # Ignore regex group 5 & 6 for client_max_window_bits + break + else: + if match.group(6): + compress = int(match.group(6)) + # Group5 must match if group6 matches + # Compress wbit 8 does not support in zlib + # If compress level not support, + # FAIL the parse progress + if compress > 15 or compress < 9: + raise WSHandshakeError("Invalid window size") + if match.group(2): + notakeover = True + # Ignore regex group 5 & 6 for client_max_window_bits + break + # Return Fail if client side and not match + elif not isserver: + raise WSHandshakeError("Extension for deflate not supported" + ext.group(1)) + + return compress, notakeover + + +def ws_ext_gen( + compress: int = 15, isserver: bool = False, server_notakeover: bool = False +) -> str: + # client_notakeover=False not used for server + # compress wbit 8 does not support in zlib + if compress < 9 or compress > 15: + raise ValueError( + "Compress wbits must between 9 and 15, " "zlib does not support wbits=8" + ) + enabledext = ["permessage-deflate"] + if not isserver: + enabledext.append("client_max_window_bits") + + if compress < 15: + enabledext.append("server_max_window_bits=" + str(compress)) + if server_notakeover: + enabledext.append("server_no_context_takeover") + # if client_notakeover: + # enabledext.append('client_no_context_takeover') + return "; ".join(enabledext) + + +class WSParserState(IntEnum): + READ_HEADER = 1 + READ_PAYLOAD_LENGTH = 2 + READ_PAYLOAD_MASK = 3 + READ_PAYLOAD = 4 + + +class WebSocketReader: + def __init__( + self, queue: DataQueue[WSMessage], max_msg_size: int, compress: bool = True + ) -> None: + self.queue = queue + self._max_msg_size = max_msg_size + + self._exc: Optional[BaseException] = None + self._partial = bytearray() + self._state = WSParserState.READ_HEADER + + self._opcode: Optional[int] = None + self._frame_fin = False + self._frame_opcode: Optional[int] = None + self._frame_payload = bytearray() + + self._tail = b"" + self._has_mask = False + self._frame_mask: Optional[bytes] = None + self._payload_length = 0 + self._payload_length_flag = 0 + self._compressed: Optional[bool] = None + self._decompressobj: Any = None # zlib.decompressobj actually + self._compress = compress + + def feed_eof(self) -> None: + self.queue.feed_eof() + + def feed_data(self, data: bytes) -> Tuple[bool, bytes]: + if self._exc: + return True, data + + try: + return self._feed_data(data) + except Exception as exc: + self._exc = exc + self.queue.set_exception(exc) + return True, b"" + + def _feed_data(self, data: bytes) -> Tuple[bool, bytes]: + for fin, opcode, payload, compressed in self.parse_frame(data): + if compressed and not self._decompressobj: + self._decompressobj = zlib.decompressobj(wbits=-zlib.MAX_WBITS) + if opcode == WSMsgType.CLOSE: + if len(payload) >= 2: + close_code = UNPACK_CLOSE_CODE(payload[:2])[0] + if close_code < 3000 and close_code not in ALLOWED_CLOSE_CODES: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + f"Invalid close code: {close_code}", + ) + try: + close_message = payload[2:].decode("utf-8") + except UnicodeDecodeError as exc: + raise WebSocketError( + WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message" + ) from exc + msg = WSMessage(WSMsgType.CLOSE, close_code, close_message) + elif payload: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + f"Invalid close frame: {fin} {opcode} {payload!r}", + ) + else: + msg = WSMessage(WSMsgType.CLOSE, 0, "") + + self.queue.feed_data(msg, 0) + + elif opcode == WSMsgType.PING: + self.queue.feed_data( + WSMessage(WSMsgType.PING, payload, ""), len(payload) + ) + + elif opcode == WSMsgType.PONG: + self.queue.feed_data( + WSMessage(WSMsgType.PONG, payload, ""), len(payload) + ) + + elif ( + opcode not in (WSMsgType.TEXT, WSMsgType.BINARY) + and self._opcode is None + ): + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, f"Unexpected opcode={opcode!r}" + ) + else: + # load text/binary + if not fin: + # got partial frame payload + if opcode != WSMsgType.CONTINUATION: + self._opcode = opcode + self._partial.extend(payload) + if self._max_msg_size and len(self._partial) >= self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + "Message size {} exceeds limit {}".format( + len(self._partial), self._max_msg_size + ), + ) + else: + # previous frame was non finished + # we should get continuation opcode + if self._partial: + if opcode != WSMsgType.CONTINUATION: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "The opcode in non-fin frame is expected " + "to be zero, got {!r}".format(opcode), + ) + + if opcode == WSMsgType.CONTINUATION: + assert self._opcode is not None + opcode = self._opcode + self._opcode = None + + self._partial.extend(payload) + if self._max_msg_size and len(self._partial) >= self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + "Message size {} exceeds limit {}".format( + len(self._partial), self._max_msg_size + ), + ) + + # Decompress process must to be done after all packets + # received. + if compressed: + self._partial.extend(_WS_DEFLATE_TRAILING) + payload_merged = self._decompressobj.decompress( + self._partial, self._max_msg_size + ) + if self._decompressobj.unconsumed_tail: + left = len(self._decompressobj.unconsumed_tail) + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + "Decompressed message size {} exceeds limit {}".format( + self._max_msg_size + left, self._max_msg_size + ), + ) + else: + payload_merged = bytes(self._partial) + + self._partial.clear() + + if opcode == WSMsgType.TEXT: + try: + text = payload_merged.decode("utf-8") + self.queue.feed_data( + WSMessage(WSMsgType.TEXT, text, ""), len(text) + ) + except UnicodeDecodeError as exc: + raise WebSocketError( + WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message" + ) from exc + else: + self.queue.feed_data( + WSMessage(WSMsgType.BINARY, payload_merged, ""), + len(payload_merged), + ) + + return False, b"" + + def parse_frame( + self, buf: bytes + ) -> List[Tuple[bool, Optional[int], bytearray, Optional[bool]]]: + """Return the next frame from the socket.""" + frames = [] + if self._tail: + buf, self._tail = self._tail + buf, b"" + + start_pos = 0 + buf_length = len(buf) + + while True: + # read header + if self._state == WSParserState.READ_HEADER: + if buf_length - start_pos >= 2: + data = buf[start_pos : start_pos + 2] + start_pos += 2 + first_byte, second_byte = data + + fin = (first_byte >> 7) & 1 + rsv1 = (first_byte >> 6) & 1 + rsv2 = (first_byte >> 5) & 1 + rsv3 = (first_byte >> 4) & 1 + opcode = first_byte & 0xF + + # frame-fin = %x0 ; more frames of this message follow + # / %x1 ; final frame of this message + # frame-rsv1 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv2 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv3 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # + # Remove rsv1 from this test for deflate development + if rsv2 or rsv3 or (rsv1 and not self._compress): + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received frame with non-zero reserved bits", + ) + + if opcode > 0x7 and fin == 0: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received fragmented control frame", + ) + + has_mask = (second_byte >> 7) & 1 + length = second_byte & 0x7F + + # Control frames MUST have a payload + # length of 125 bytes or less + if opcode > 0x7 and length > 125: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Control frame payload cannot be " "larger than 125 bytes", + ) + + # Set compress status if last package is FIN + # OR set compress status if this is first fragment + # Raise error if not first fragment with rsv1 = 0x1 + if self._frame_fin or self._compressed is None: + self._compressed = True if rsv1 else False + elif rsv1: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received frame with non-zero reserved bits", + ) + + self._frame_fin = bool(fin) + self._frame_opcode = opcode + self._has_mask = bool(has_mask) + self._payload_length_flag = length + self._state = WSParserState.READ_PAYLOAD_LENGTH + else: + break + + # read payload length + if self._state == WSParserState.READ_PAYLOAD_LENGTH: + length = self._payload_length_flag + if length == 126: + if buf_length - start_pos >= 2: + data = buf[start_pos : start_pos + 2] + start_pos += 2 + length = UNPACK_LEN2(data)[0] + self._payload_length = length + self._state = ( + WSParserState.READ_PAYLOAD_MASK + if self._has_mask + else WSParserState.READ_PAYLOAD + ) + else: + break + elif length > 126: + if buf_length - start_pos >= 8: + data = buf[start_pos : start_pos + 8] + start_pos += 8 + length = UNPACK_LEN3(data)[0] + self._payload_length = length + self._state = ( + WSParserState.READ_PAYLOAD_MASK + if self._has_mask + else WSParserState.READ_PAYLOAD + ) + else: + break + else: + self._payload_length = length + self._state = ( + WSParserState.READ_PAYLOAD_MASK + if self._has_mask + else WSParserState.READ_PAYLOAD + ) + + # read payload mask + if self._state == WSParserState.READ_PAYLOAD_MASK: + if buf_length - start_pos >= 4: + self._frame_mask = buf[start_pos : start_pos + 4] + start_pos += 4 + self._state = WSParserState.READ_PAYLOAD + else: + break + + if self._state == WSParserState.READ_PAYLOAD: + length = self._payload_length + payload = self._frame_payload + + chunk_len = buf_length - start_pos + if length >= chunk_len: + self._payload_length = length - chunk_len + payload.extend(buf[start_pos:]) + start_pos = buf_length + else: + self._payload_length = 0 + payload.extend(buf[start_pos : start_pos + length]) + start_pos = start_pos + length + + if self._payload_length == 0: + if self._has_mask: + assert self._frame_mask is not None + _websocket_mask(self._frame_mask, payload) + + frames.append( + (self._frame_fin, self._frame_opcode, payload, self._compressed) + ) + + self._frame_payload = bytearray() + self._state = WSParserState.READ_HEADER + else: + break + + self._tail = buf[start_pos:] + + return frames + + +class WebSocketWriter: + def __init__( + self, + protocol: BaseProtocol, + transport: asyncio.Transport, + *, + use_mask: bool = False, + limit: int = DEFAULT_LIMIT, + random: Any = random.Random(), + compress: int = 0, + notakeover: bool = False, + ) -> None: + self.protocol = protocol + self.transport = transport + self.use_mask = use_mask + self.randrange = random.randrange + self.compress = compress + self.notakeover = notakeover + self._closing = False + self._limit = limit + self._output_size = 0 + self._compressobj: Any = None # actually compressobj + + async def _send_frame( + self, message: bytes, opcode: int, compress: Optional[int] = None + ) -> None: + """Send a frame over the websocket with message as its payload.""" + if self._closing and not (opcode & WSMsgType.CLOSE): + raise ConnectionResetError("Cannot write to closing transport") + + rsv = 0 + + # Only compress larger packets (disabled) + # Does small packet needs to be compressed? + # if self.compress and opcode < 8 and len(message) > 124: + if (compress or self.compress) and opcode < 8: + if compress: + # Do not set self._compress if compressing is for this frame + compressobj = zlib.compressobj(level=zlib.Z_BEST_SPEED, wbits=-compress) + else: # self.compress + if not self._compressobj: + self._compressobj = zlib.compressobj( + level=zlib.Z_BEST_SPEED, wbits=-self.compress + ) + compressobj = self._compressobj + + message = compressobj.compress(message) + message = message + compressobj.flush( + zlib.Z_FULL_FLUSH if self.notakeover else zlib.Z_SYNC_FLUSH + ) + if message.endswith(_WS_DEFLATE_TRAILING): + message = message[:-4] + rsv = rsv | 0x40 + + msg_length = len(message) + + use_mask = self.use_mask + if use_mask: + mask_bit = 0x80 + else: + mask_bit = 0 + + if msg_length < 126: + header = PACK_LEN1(0x80 | rsv | opcode, msg_length | mask_bit) + elif msg_length < (1 << 16): + header = PACK_LEN2(0x80 | rsv | opcode, 126 | mask_bit, msg_length) + else: + header = PACK_LEN3(0x80 | rsv | opcode, 127 | mask_bit, msg_length) + if use_mask: + mask = self.randrange(0, 0xFFFFFFFF) + mask = mask.to_bytes(4, "big") + message = bytearray(message) + _websocket_mask(mask, message) + self._write(header + mask + message) + self._output_size += len(header) + len(mask) + len(message) + else: + if len(message) > MSG_SIZE: + self._write(header) + self._write(message) + else: + self._write(header + message) + + self._output_size += len(header) + len(message) + + if self._output_size > self._limit: + self._output_size = 0 + await self.protocol._drain_helper() + + def _write(self, data: bytes) -> None: + if self.transport is None or self.transport.is_closing(): + raise ConnectionResetError("Cannot write to closing transport") + self.transport.write(data) + + async def pong(self, message: bytes = b"") -> None: + """Send pong message.""" + if isinstance(message, str): + message = message.encode("utf-8") + await self._send_frame(message, WSMsgType.PONG) + + async def ping(self, message: bytes = b"") -> None: + """Send ping message.""" + if isinstance(message, str): + message = message.encode("utf-8") + await self._send_frame(message, WSMsgType.PING) + + async def send( + self, + message: Union[str, bytes], + binary: bool = False, + compress: Optional[int] = None, + ) -> None: + """Send a frame over the websocket with message as its payload.""" + if isinstance(message, str): + message = message.encode("utf-8") + if binary: + await self._send_frame(message, WSMsgType.BINARY, compress) + else: + await self._send_frame(message, WSMsgType.TEXT, compress) + + async def close(self, code: int = 1000, message: bytes = b"") -> None: + """Close the websocket, sending the specified code and message.""" + if isinstance(message, str): + message = message.encode("utf-8") + try: + await self._send_frame( + PACK_CLOSE_CODE(code) + message, opcode=WSMsgType.CLOSE + ) + finally: + self._closing = True diff --git a/lib/aiohttp/http_writer.py b/lib/aiohttp/http_writer.py new file mode 100644 index 0000000..73f0f96 --- /dev/null +++ b/lib/aiohttp/http_writer.py @@ -0,0 +1,198 @@ +"""Http related parsers and protocol.""" + +import asyncio +import zlib +from typing import Any, Awaitable, Callable, NamedTuple, Optional, Union # noqa + +from multidict import CIMultiDict + +from .abc import AbstractStreamWriter +from .base_protocol import BaseProtocol +from .helpers import NO_EXTENSIONS + +__all__ = ("StreamWriter", "HttpVersion", "HttpVersion10", "HttpVersion11") + + +class HttpVersion(NamedTuple): + major: int + minor: int + + +HttpVersion10 = HttpVersion(1, 0) +HttpVersion11 = HttpVersion(1, 1) + + +_T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] +_T_OnHeadersSent = Optional[Callable[["CIMultiDict[str]"], Awaitable[None]]] + + +class StreamWriter(AbstractStreamWriter): + def __init__( + self, + protocol: BaseProtocol, + loop: asyncio.AbstractEventLoop, + on_chunk_sent: _T_OnChunkSent = None, + on_headers_sent: _T_OnHeadersSent = None, + ) -> None: + self._protocol = protocol + + self.loop = loop + self.length = None + self.chunked = False + self.buffer_size = 0 + self.output_size = 0 + + self._eof = False + self._compress: Any = None + self._drain_waiter = None + + self._on_chunk_sent: _T_OnChunkSent = on_chunk_sent + self._on_headers_sent: _T_OnHeadersSent = on_headers_sent + + @property + def transport(self) -> Optional[asyncio.Transport]: + return self._protocol.transport + + @property + def protocol(self) -> BaseProtocol: + return self._protocol + + def enable_chunking(self) -> None: + self.chunked = True + + def enable_compression( + self, encoding: str = "deflate", strategy: int = zlib.Z_DEFAULT_STRATEGY + ) -> None: + zlib_mode = 16 + zlib.MAX_WBITS if encoding == "gzip" else zlib.MAX_WBITS + self._compress = zlib.compressobj(wbits=zlib_mode, strategy=strategy) + + def _write(self, chunk: bytes) -> None: + size = len(chunk) + self.buffer_size += size + self.output_size += size + transport = self.transport + if not self._protocol.connected or transport is None or transport.is_closing(): + raise ConnectionResetError("Cannot write to closing transport") + transport.write(chunk) + + async def write( + self, chunk: bytes, *, drain: bool = True, LIMIT: int = 0x10000 + ) -> None: + """Writes chunk of data to a stream. + + write_eof() indicates end of stream. + writer can't be used after write_eof() method being called. + write() return drain future. + """ + if self._on_chunk_sent is not None: + await self._on_chunk_sent(chunk) + + if isinstance(chunk, memoryview): + if chunk.nbytes != len(chunk): + # just reshape it + chunk = chunk.cast("c") + + if self._compress is not None: + chunk = self._compress.compress(chunk) + if not chunk: + return + + if self.length is not None: + chunk_len = len(chunk) + if self.length >= chunk_len: + self.length = self.length - chunk_len + else: + chunk = chunk[: self.length] + self.length = 0 + if not chunk: + return + + if chunk: + if self.chunked: + chunk_len_pre = ("%x\r\n" % len(chunk)).encode("ascii") + chunk = chunk_len_pre + chunk + b"\r\n" + + self._write(chunk) + + if self.buffer_size > LIMIT and drain: + self.buffer_size = 0 + await self.drain() + + async def write_headers( + self, status_line: str, headers: "CIMultiDict[str]" + ) -> None: + """Write request/response status and headers.""" + if self._on_headers_sent is not None: + await self._on_headers_sent(headers) + + # status + headers + buf = _serialize_headers(status_line, headers) + self._write(buf) + + async def write_eof(self, chunk: bytes = b"") -> None: + if self._eof: + return + + if chunk and self._on_chunk_sent is not None: + await self._on_chunk_sent(chunk) + + if self._compress: + if chunk: + chunk = self._compress.compress(chunk) + + chunk = chunk + self._compress.flush() + if chunk and self.chunked: + chunk_len = ("%x\r\n" % len(chunk)).encode("ascii") + chunk = chunk_len + chunk + b"\r\n0\r\n\r\n" + else: + if self.chunked: + if chunk: + chunk_len = ("%x\r\n" % len(chunk)).encode("ascii") + chunk = chunk_len + chunk + b"\r\n0\r\n\r\n" + else: + chunk = b"0\r\n\r\n" + + if chunk: + self._write(chunk) + + await self.drain() + + self._eof = True + + async def drain(self) -> None: + """Flush the write buffer. + + The intended use is to write + + await w.write(data) + await w.drain() + """ + if self._protocol.transport is not None: + await self._protocol._drain_helper() + + +def _safe_header(string: str) -> str: + if "\r" in string or "\n" in string: + raise ValueError( + "Newline or carriage return detected in headers. " + "Potential header injection attack." + ) + return string + + +def _py_serialize_headers(status_line: str, headers: "CIMultiDict[str]") -> bytes: + headers_gen = (_safe_header(k) + ": " + _safe_header(v) for k, v in headers.items()) + line = status_line + "\r\n" + "\r\n".join(headers_gen) + "\r\n\r\n" + return line.encode("utf-8") + + +_serialize_headers = _py_serialize_headers + +try: + import aiohttp._http_writer as _http_writer # type: ignore[import] + + _c_serialize_headers = _http_writer._serialize_headers + if not NO_EXTENSIONS: + _serialize_headers = _c_serialize_headers +except ImportError: + pass diff --git a/lib/aiohttp/locks.py b/lib/aiohttp/locks.py new file mode 100644 index 0000000..de2dc83 --- /dev/null +++ b/lib/aiohttp/locks.py @@ -0,0 +1,41 @@ +import asyncio +import collections +from typing import Any, Deque, Optional + + +class EventResultOrError: + """Event asyncio lock helper class. + + Wraps the Event asyncio lock allowing either to awake the + locked Tasks without any error or raising an exception. + + thanks to @vorpalsmith for the simple design. + """ + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop = loop + self._exc: Optional[BaseException] = None + self._event = asyncio.Event() + self._waiters: Deque[asyncio.Future[Any]] = collections.deque() + + def set(self, exc: Optional[BaseException] = None) -> None: + self._exc = exc + self._event.set() + + async def wait(self) -> Any: + waiter = self._loop.create_task(self._event.wait()) + self._waiters.append(waiter) + try: + val = await waiter + finally: + self._waiters.remove(waiter) + + if self._exc is not None: + raise self._exc + + return val + + def cancel(self) -> None: + """Cancel all waiters""" + for waiter in self._waiters: + waiter.cancel() diff --git a/lib/aiohttp/log.py b/lib/aiohttp/log.py new file mode 100644 index 0000000..3cecea2 --- /dev/null +++ b/lib/aiohttp/log.py @@ -0,0 +1,8 @@ +import logging + +access_logger = logging.getLogger("aiohttp.access") +client_logger = logging.getLogger("aiohttp.client") +internal_logger = logging.getLogger("aiohttp.internal") +server_logger = logging.getLogger("aiohttp.server") +web_logger = logging.getLogger("aiohttp.web") +ws_logger = logging.getLogger("aiohttp.websocket") diff --git a/lib/aiohttp/multipart.py b/lib/aiohttp/multipart.py new file mode 100644 index 0000000..73801f4 --- /dev/null +++ b/lib/aiohttp/multipart.py @@ -0,0 +1,961 @@ +import base64 +import binascii +import json +import re +import uuid +import warnings +import zlib +from collections import deque +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + AsyncIterator, + Deque, + Dict, + Iterator, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) +from urllib.parse import parse_qsl, unquote, urlencode + +from multidict import CIMultiDict, CIMultiDictProxy, MultiMapping + +from .hdrs import ( + CONTENT_DISPOSITION, + CONTENT_ENCODING, + CONTENT_LENGTH, + CONTENT_TRANSFER_ENCODING, + CONTENT_TYPE, +) +from .helpers import CHAR, TOKEN, parse_mimetype, reify +from .http import HeadersParser +from .payload import ( + JsonPayload, + LookupError, + Order, + Payload, + StringPayload, + get_payload, + payload_type, +) +from .streams import StreamReader + +__all__ = ( + "MultipartReader", + "MultipartWriter", + "BodyPartReader", + "BadContentDispositionHeader", + "BadContentDispositionParam", + "parse_content_disposition", + "content_disposition_filename", +) + + +if TYPE_CHECKING: # pragma: no cover + from .client_reqrep import ClientResponse + + +class BadContentDispositionHeader(RuntimeWarning): + pass + + +class BadContentDispositionParam(RuntimeWarning): + pass + + +def parse_content_disposition( + header: Optional[str], +) -> Tuple[Optional[str], Dict[str, str]]: + def is_token(string: str) -> bool: + return bool(string) and TOKEN >= set(string) + + def is_quoted(string: str) -> bool: + return string[0] == string[-1] == '"' + + def is_rfc5987(string: str) -> bool: + return is_token(string) and string.count("'") == 2 + + def is_extended_param(string: str) -> bool: + return string.endswith("*") + + def is_continuous_param(string: str) -> bool: + pos = string.find("*") + 1 + if not pos: + return False + substring = string[pos:-1] if string.endswith("*") else string[pos:] + return substring.isdigit() + + def unescape(text: str, *, chars: str = "".join(map(re.escape, CHAR))) -> str: + return re.sub(f"\\\\([{chars}])", "\\1", text) + + if not header: + return None, {} + + disptype, *parts = header.split(";") + if not is_token(disptype): + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + params: Dict[str, str] = {} + while parts: + item = parts.pop(0) + + if "=" not in item: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + key, value = item.split("=", 1) + key = key.lower().strip() + value = value.lstrip() + + if key in params: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + if not is_token(key): + warnings.warn(BadContentDispositionParam(item)) + continue + + elif is_continuous_param(key): + if is_quoted(value): + value = unescape(value[1:-1]) + elif not is_token(value): + warnings.warn(BadContentDispositionParam(item)) + continue + + elif is_extended_param(key): + if is_rfc5987(value): + encoding, _, value = value.split("'", 2) + encoding = encoding or "utf-8" + else: + warnings.warn(BadContentDispositionParam(item)) + continue + + try: + value = unquote(value, encoding, "strict") + except UnicodeDecodeError: # pragma: nocover + warnings.warn(BadContentDispositionParam(item)) + continue + + else: + failed = True + if is_quoted(value): + failed = False + value = unescape(value[1:-1].lstrip("\\/")) + elif is_token(value): + failed = False + elif parts: + # maybe just ; in filename, in any case this is just + # one case fix, for proper fix we need to redesign parser + _value = f"{value};{parts[0]}" + if is_quoted(_value): + parts.pop(0) + value = unescape(_value[1:-1].lstrip("\\/")) + failed = False + + if failed: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + params[key] = value + + return disptype.lower(), params + + +def content_disposition_filename( + params: Mapping[str, str], name: str = "filename" +) -> Optional[str]: + name_suf = "%s*" % name + if not params: + return None + elif name_suf in params: + return params[name_suf] + elif name in params: + return params[name] + else: + parts = [] + fnparams = sorted( + (key, value) for key, value in params.items() if key.startswith(name_suf) + ) + for num, (key, value) in enumerate(fnparams): + _, tail = key.split("*", 1) + if tail.endswith("*"): + tail = tail[:-1] + if tail == str(num): + parts.append(value) + else: + break + if not parts: + return None + value = "".join(parts) + if "'" in value: + encoding, _, value = value.split("'", 2) + encoding = encoding or "utf-8" + return unquote(value, encoding, "strict") + return value + + +class MultipartResponseWrapper: + """Wrapper around the MultipartReader. + + It takes care about + underlying connection and close it when it needs in. + """ + + def __init__( + self, + resp: "ClientResponse", + stream: "MultipartReader", + ) -> None: + self.resp = resp + self.stream = stream + + def __aiter__(self) -> "MultipartResponseWrapper": + return self + + async def __anext__( + self, + ) -> Union["MultipartReader", "BodyPartReader"]: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + def at_eof(self) -> bool: + """Returns True when all response data had been read.""" + return self.resp.content.at_eof() + + async def next( + self, + ) -> Optional[Union["MultipartReader", "BodyPartReader"]]: + """Emits next multipart reader object.""" + item = await self.stream.next() + if self.stream.at_eof(): + await self.release() + return item + + async def release(self) -> None: + """Release the connection gracefully. + + All remaining content is read to the void. + """ + await self.resp.release() + + +class BodyPartReader: + """Multipart reader for single body part.""" + + chunk_size = 8192 + + def __init__( + self, boundary: bytes, headers: "CIMultiDictProxy[str]", content: StreamReader + ) -> None: + self.headers = headers + self._boundary = boundary + self._content = content + self._at_eof = False + length = self.headers.get(CONTENT_LENGTH, None) + self._length = int(length) if length is not None else None + self._read_bytes = 0 + # TODO: typeing.Deque is not supported by Python 3.5 + self._unread: Deque[bytes] = deque() + self._prev_chunk: Optional[bytes] = None + self._content_eof = 0 + self._cache: Dict[str, Any] = {} + + def __aiter__(self) -> AsyncIterator["BodyPartReader"]: + return self # type: ignore[return-value] + + async def __anext__(self) -> bytes: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + async def next(self) -> Optional[bytes]: + item = await self.read() + if not item: + return None + return item + + async def read(self, *, decode: bool = False) -> bytes: + """Reads body part data. + + decode: Decodes data following by encoding + method from Content-Encoding header. If it missed + data remains untouched + """ + if self._at_eof: + return b"" + data = bytearray() + while not self._at_eof: + data.extend(await self.read_chunk(self.chunk_size)) + if decode: + return self.decode(data) + return data + + async def read_chunk(self, size: int = chunk_size) -> bytes: + """Reads body part content chunk of the specified size. + + size: chunk size + """ + if self._at_eof: + return b"" + if self._length: + chunk = await self._read_chunk_from_length(size) + else: + chunk = await self._read_chunk_from_stream(size) + + self._read_bytes += len(chunk) + if self._read_bytes == self._length: + self._at_eof = True + if self._at_eof: + clrf = await self._content.readline() + assert ( + b"\r\n" == clrf + ), "reader did not read all the data or it is malformed" + return chunk + + async def _read_chunk_from_length(self, size: int) -> bytes: + # Reads body part content chunk of the specified size. + # The body part must has Content-Length header with proper value. + assert self._length is not None, "Content-Length required for chunked read" + chunk_size = min(size, self._length - self._read_bytes) + chunk = await self._content.read(chunk_size) + return chunk + + async def _read_chunk_from_stream(self, size: int) -> bytes: + # Reads content chunk of body part with unknown length. + # The Content-Length header for body part is not necessary. + assert ( + size >= len(self._boundary) + 2 + ), "Chunk size must be greater or equal than boundary length + 2" + first_chunk = self._prev_chunk is None + if first_chunk: + self._prev_chunk = await self._content.read(size) + + chunk = await self._content.read(size) + self._content_eof += int(self._content.at_eof()) + assert self._content_eof < 3, "Reading after EOF" + assert self._prev_chunk is not None + window = self._prev_chunk + chunk + sub = b"\r\n" + self._boundary + if first_chunk: + idx = window.find(sub) + else: + idx = window.find(sub, max(0, len(self._prev_chunk) - len(sub))) + if idx >= 0: + # pushing boundary back to content + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + self._content.unread_data(window[idx:]) + if size > idx: + self._prev_chunk = self._prev_chunk[:idx] + chunk = window[len(self._prev_chunk) : idx] + if not chunk: + self._at_eof = True + result = self._prev_chunk + self._prev_chunk = chunk + return result + + async def readline(self) -> bytes: + """Reads body part by line by line.""" + if self._at_eof: + return b"" + + if self._unread: + line = self._unread.popleft() + else: + line = await self._content.readline() + + if line.startswith(self._boundary): + # the very last boundary may not come with \r\n, + # so set single rules for everyone + sline = line.rstrip(b"\r\n") + boundary = self._boundary + last_boundary = self._boundary + b"--" + # ensure that we read exactly the boundary, not something alike + if sline == boundary or sline == last_boundary: + self._at_eof = True + self._unread.append(line) + return b"" + else: + next_line = await self._content.readline() + if next_line.startswith(self._boundary): + line = line[:-2] # strip CRLF but only once + self._unread.append(next_line) + + return line + + async def release(self) -> None: + """Like read(), but reads all the data to the void.""" + if self._at_eof: + return + while not self._at_eof: + await self.read_chunk(self.chunk_size) + + async def text(self, *, encoding: Optional[str] = None) -> str: + """Like read(), but assumes that body part contains text data.""" + data = await self.read(decode=True) + # see https://www.w3.org/TR/html5/forms.html#multipart/form-data-encoding-algorithm # NOQA + # and https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-send # NOQA + encoding = encoding or self.get_charset(default="utf-8") + return data.decode(encoding) + + async def json(self, *, encoding: Optional[str] = None) -> Optional[Dict[str, Any]]: + """Like read(), but assumes that body parts contains JSON data.""" + data = await self.read(decode=True) + if not data: + return None + encoding = encoding or self.get_charset(default="utf-8") + return cast(Dict[str, Any], json.loads(data.decode(encoding))) + + async def form(self, *, encoding: Optional[str] = None) -> List[Tuple[str, str]]: + """Like read(), but assumes that body parts contain form urlencoded data.""" + data = await self.read(decode=True) + if not data: + return [] + if encoding is not None: + real_encoding = encoding + else: + real_encoding = self.get_charset(default="utf-8") + return parse_qsl( + data.rstrip().decode(real_encoding), + keep_blank_values=True, + encoding=real_encoding, + ) + + def at_eof(self) -> bool: + """Returns True if the boundary was reached or False otherwise.""" + return self._at_eof + + def decode(self, data: bytes) -> bytes: + """Decodes data. + + Decoding is done according the specified Content-Encoding + or Content-Transfer-Encoding headers value. + """ + if CONTENT_TRANSFER_ENCODING in self.headers: + data = self._decode_content_transfer(data) + if CONTENT_ENCODING in self.headers: + return self._decode_content(data) + return data + + def _decode_content(self, data: bytes) -> bytes: + encoding = self.headers.get(CONTENT_ENCODING, "").lower() + + if encoding == "deflate": + return zlib.decompress(data, -zlib.MAX_WBITS) + elif encoding == "gzip": + return zlib.decompress(data, 16 + zlib.MAX_WBITS) + elif encoding == "identity": + return data + else: + raise RuntimeError(f"unknown content encoding: {encoding}") + + def _decode_content_transfer(self, data: bytes) -> bytes: + encoding = self.headers.get(CONTENT_TRANSFER_ENCODING, "").lower() + + if encoding == "base64": + return base64.b64decode(data) + elif encoding == "quoted-printable": + return binascii.a2b_qp(data) + elif encoding in ("binary", "8bit", "7bit"): + return data + else: + raise RuntimeError( + "unknown content transfer encoding: {}" "".format(encoding) + ) + + def get_charset(self, default: str) -> str: + """Returns charset parameter from Content-Type header or default.""" + ctype = self.headers.get(CONTENT_TYPE, "") + mimetype = parse_mimetype(ctype) + return mimetype.parameters.get("charset", default) + + @reify + def name(self) -> Optional[str]: + """Returns name specified in Content-Disposition header. + + If the header is missing or malformed, returns None. + """ + _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION)) + return content_disposition_filename(params, "name") + + @reify + def filename(self) -> Optional[str]: + """Returns filename specified in Content-Disposition header. + + Returns None if the header is missing or malformed. + """ + _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION)) + return content_disposition_filename(params, "filename") + + +@payload_type(BodyPartReader, order=Order.try_first) +class BodyPartReaderPayload(Payload): + def __init__(self, value: BodyPartReader, *args: Any, **kwargs: Any) -> None: + super().__init__(value, *args, **kwargs) + + params: Dict[str, str] = {} + if value.name is not None: + params["name"] = value.name + if value.filename is not None: + params["filename"] = value.filename + + if params: + self.set_content_disposition("attachment", True, **params) + + async def write(self, writer: Any) -> None: + field = self._value + chunk = await field.read_chunk(size=2**16) + while chunk: + await writer.write(field.decode(chunk)) + chunk = await field.read_chunk(size=2**16) + + +class MultipartReader: + """Multipart body reader.""" + + #: Response wrapper, used when multipart readers constructs from response. + response_wrapper_cls = MultipartResponseWrapper + #: Multipart reader class, used to handle multipart/* body parts. + #: None points to type(self) + multipart_reader_cls = None + #: Body part reader class for non multipart/* content types. + part_reader_cls = BodyPartReader + + def __init__(self, headers: Mapping[str, str], content: StreamReader) -> None: + self.headers = headers + self._boundary = ("--" + self._get_boundary()).encode() + self._content = content + self._last_part: Optional[Union["MultipartReader", BodyPartReader]] = None + self._at_eof = False + self._at_bof = True + self._unread: List[bytes] = [] + + def __aiter__( + self, + ) -> AsyncIterator["BodyPartReader"]: + return self # type: ignore[return-value] + + async def __anext__( + self, + ) -> Optional[Union["MultipartReader", BodyPartReader]]: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + @classmethod + def from_response( + cls, + response: "ClientResponse", + ) -> MultipartResponseWrapper: + """Constructs reader instance from HTTP response. + + :param response: :class:`~aiohttp.client.ClientResponse` instance + """ + obj = cls.response_wrapper_cls( + response, cls(response.headers, response.content) + ) + return obj + + def at_eof(self) -> bool: + """Returns True if the final boundary was reached, false otherwise.""" + return self._at_eof + + async def next( + self, + ) -> Optional[Union["MultipartReader", BodyPartReader]]: + """Emits the next multipart body part.""" + # So, if we're at BOF, we need to skip till the boundary. + if self._at_eof: + return None + await self._maybe_release_last_part() + if self._at_bof: + await self._read_until_first_boundary() + self._at_bof = False + else: + await self._read_boundary() + if self._at_eof: # we just read the last boundary, nothing to do there + return None + self._last_part = await self.fetch_next_part() + return self._last_part + + async def release(self) -> None: + """Reads all the body parts to the void till the final boundary.""" + while not self._at_eof: + item = await self.next() + if item is None: + break + await item.release() + + async def fetch_next_part( + self, + ) -> Union["MultipartReader", BodyPartReader]: + """Returns the next body part reader.""" + headers = await self._read_headers() + return self._get_part_reader(headers) + + def _get_part_reader( + self, + headers: "CIMultiDictProxy[str]", + ) -> Union["MultipartReader", BodyPartReader]: + """Dispatches the response by the `Content-Type` header. + + Returns a suitable reader instance. + + :param dict headers: Response headers + """ + ctype = headers.get(CONTENT_TYPE, "") + mimetype = parse_mimetype(ctype) + + if mimetype.type == "multipart": + if self.multipart_reader_cls is None: + return type(self)(headers, self._content) + return self.multipart_reader_cls(headers, self._content) + else: + return self.part_reader_cls(self._boundary, headers, self._content) + + def _get_boundary(self) -> str: + mimetype = parse_mimetype(self.headers[CONTENT_TYPE]) + + assert mimetype.type == "multipart", "multipart/* content type expected" + + if "boundary" not in mimetype.parameters: + raise ValueError( + "boundary missed for Content-Type: %s" % self.headers[CONTENT_TYPE] + ) + + boundary = mimetype.parameters["boundary"] + if len(boundary) > 70: + raise ValueError("boundary %r is too long (70 chars max)" % boundary) + + return boundary + + async def _readline(self) -> bytes: + if self._unread: + return self._unread.pop() + return await self._content.readline() + + async def _read_until_first_boundary(self) -> None: + while True: + chunk = await self._readline() + if chunk == b"": + raise ValueError( + "Could not find starting boundary %r" % (self._boundary) + ) + chunk = chunk.rstrip() + if chunk == self._boundary: + return + elif chunk == self._boundary + b"--": + self._at_eof = True + return + + async def _read_boundary(self) -> None: + chunk = (await self._readline()).rstrip() + if chunk == self._boundary: + pass + elif chunk == self._boundary + b"--": + self._at_eof = True + epilogue = await self._readline() + next_line = await self._readline() + + # the epilogue is expected and then either the end of input or the + # parent multipart boundary, if the parent boundary is found then + # it should be marked as unread and handed to the parent for + # processing + if next_line[:2] == b"--": + self._unread.append(next_line) + # otherwise the request is likely missing an epilogue and both + # lines should be passed to the parent for processing + # (this handles the old behavior gracefully) + else: + self._unread.extend([next_line, epilogue]) + else: + raise ValueError(f"Invalid boundary {chunk!r}, expected {self._boundary!r}") + + async def _read_headers(self) -> "CIMultiDictProxy[str]": + lines = [b""] + while True: + chunk = await self._content.readline() + chunk = chunk.strip() + lines.append(chunk) + if not chunk: + break + parser = HeadersParser() + headers, raw_headers = parser.parse_headers(lines) + return headers + + async def _maybe_release_last_part(self) -> None: + """Ensures that the last read body part is read completely.""" + if self._last_part is not None: + if not self._last_part.at_eof(): + await self._last_part.release() + self._unread.extend(self._last_part._unread) + self._last_part = None + + +_Part = Tuple[Payload, str, str] + + +class MultipartWriter(Payload): + """Multipart body writer.""" + + def __init__(self, subtype: str = "mixed", boundary: Optional[str] = None) -> None: + boundary = boundary if boundary is not None else uuid.uuid4().hex + # The underlying Payload API demands a str (utf-8), not bytes, + # so we need to ensure we don't lose anything during conversion. + # As a result, require the boundary to be ASCII only. + # In both situations. + + try: + self._boundary = boundary.encode("ascii") + except UnicodeEncodeError: + raise ValueError("boundary should contain ASCII only chars") from None + ctype = f"multipart/{subtype}; boundary={self._boundary_value}" + + super().__init__(None, content_type=ctype) + + self._parts: List[_Part] = [] + + def __enter__(self) -> "MultipartWriter": + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + pass + + def __iter__(self) -> Iterator[_Part]: + return iter(self._parts) + + def __len__(self) -> int: + return len(self._parts) + + def __bool__(self) -> bool: + return True + + _valid_tchar_regex = re.compile(rb"\A[!#$%&'*+\-.^_`|~\w]+\Z") + _invalid_qdtext_char_regex = re.compile(rb"[\x00-\x08\x0A-\x1F\x7F]") + + @property + def _boundary_value(self) -> str: + """Wrap boundary parameter value in quotes, if necessary. + + Reads self.boundary and returns a unicode sting. + """ + # Refer to RFCs 7231, 7230, 5234. + # + # parameter = token "=" ( token / quoted-string ) + # token = 1*tchar + # quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + # qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text + # obs-text = %x80-FF + # quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + # / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + # / DIGIT / ALPHA + # ; any VCHAR, except delimiters + # VCHAR = %x21-7E + value = self._boundary + if re.match(self._valid_tchar_regex, value): + return value.decode("ascii") # cannot fail + + if re.search(self._invalid_qdtext_char_regex, value): + raise ValueError("boundary value contains invalid characters") + + # escape %x5C and %x22 + quoted_value_content = value.replace(b"\\", b"\\\\") + quoted_value_content = quoted_value_content.replace(b'"', b'\\"') + + return '"' + quoted_value_content.decode("ascii") + '"' + + @property + def boundary(self) -> str: + return self._boundary.decode("ascii") + + def append(self, obj: Any, headers: Optional[MultiMapping[str]] = None) -> Payload: + if headers is None: + headers = CIMultiDict() + + if isinstance(obj, Payload): + obj.headers.update(headers) + return self.append_payload(obj) + else: + try: + payload = get_payload(obj, headers=headers) + except LookupError: + raise TypeError("Cannot create payload from %r" % obj) + else: + return self.append_payload(payload) + + def append_payload(self, payload: Payload) -> Payload: + """Adds a new body part to multipart writer.""" + # compression + encoding: Optional[str] = payload.headers.get( + CONTENT_ENCODING, + "", + ).lower() + if encoding and encoding not in ("deflate", "gzip", "identity"): + raise RuntimeError(f"unknown content encoding: {encoding}") + if encoding == "identity": + encoding = None + + # te encoding + te_encoding: Optional[str] = payload.headers.get( + CONTENT_TRANSFER_ENCODING, + "", + ).lower() + if te_encoding not in ("", "base64", "quoted-printable", "binary"): + raise RuntimeError( + "unknown content transfer encoding: {}" "".format(te_encoding) + ) + if te_encoding == "binary": + te_encoding = None + + # size + size = payload.size + if size is not None and not (encoding or te_encoding): + payload.headers[CONTENT_LENGTH] = str(size) + + self._parts.append((payload, encoding, te_encoding)) # type: ignore[arg-type] + return payload + + def append_json( + self, obj: Any, headers: Optional[MultiMapping[str]] = None + ) -> Payload: + """Helper to append JSON part.""" + if headers is None: + headers = CIMultiDict() + + return self.append_payload(JsonPayload(obj, headers=headers)) + + def append_form( + self, + obj: Union[Sequence[Tuple[str, str]], Mapping[str, str]], + headers: Optional[MultiMapping[str]] = None, + ) -> Payload: + """Helper to append form urlencoded part.""" + assert isinstance(obj, (Sequence, Mapping)) + + if headers is None: + headers = CIMultiDict() + + if isinstance(obj, Mapping): + obj = list(obj.items()) + data = urlencode(obj, doseq=True) + + return self.append_payload( + StringPayload( + data, headers=headers, content_type="application/x-www-form-urlencoded" + ) + ) + + @property + def size(self) -> Optional[int]: + """Size of the payload.""" + total = 0 + for part, encoding, te_encoding in self._parts: + if encoding or te_encoding or part.size is None: + return None + + total += int( + 2 + + len(self._boundary) + + 2 + + part.size # b'--'+self._boundary+b'\r\n' + + len(part._binary_headers) + + 2 # b'\r\n' + ) + + total += 2 + len(self._boundary) + 4 # b'--'+self._boundary+b'--\r\n' + return total + + async def write(self, writer: Any, close_boundary: bool = True) -> None: + """Write body.""" + for part, encoding, te_encoding in self._parts: + await writer.write(b"--" + self._boundary + b"\r\n") + await writer.write(part._binary_headers) + + if encoding or te_encoding: + w = MultipartPayloadWriter(writer) + if encoding: + w.enable_compression(encoding) + if te_encoding: + w.enable_encoding(te_encoding) + await part.write(w) # type: ignore[arg-type] + await w.write_eof() + else: + await part.write(writer) + + await writer.write(b"\r\n") + + if close_boundary: + await writer.write(b"--" + self._boundary + b"--\r\n") + + +class MultipartPayloadWriter: + def __init__(self, writer: Any) -> None: + self._writer = writer + self._encoding: Optional[str] = None + self._compress: Any = None + self._encoding_buffer: Optional[bytearray] = None + + def enable_encoding(self, encoding: str) -> None: + if encoding == "base64": + self._encoding = encoding + self._encoding_buffer = bytearray() + elif encoding == "quoted-printable": + self._encoding = "quoted-printable" + + def enable_compression( + self, encoding: str = "deflate", strategy: int = zlib.Z_DEFAULT_STRATEGY + ) -> None: + zlib_mode = 16 + zlib.MAX_WBITS if encoding == "gzip" else -zlib.MAX_WBITS + self._compress = zlib.compressobj(wbits=zlib_mode, strategy=strategy) + + async def write_eof(self) -> None: + if self._compress is not None: + chunk = self._compress.flush() + if chunk: + self._compress = None + await self.write(chunk) + + if self._encoding == "base64": + if self._encoding_buffer: + await self._writer.write(base64.b64encode(self._encoding_buffer)) + + async def write(self, chunk: bytes) -> None: + if self._compress is not None: + if chunk: + chunk = self._compress.compress(chunk) + if not chunk: + return + + if self._encoding == "base64": + buf = self._encoding_buffer + assert buf is not None + buf.extend(chunk) + + if buf: + div, mod = divmod(len(buf), 3) + enc_chunk, self._encoding_buffer = (buf[: div * 3], buf[div * 3 :]) + if enc_chunk: + b64chunk = base64.b64encode(enc_chunk) + await self._writer.write(b64chunk) + elif self._encoding == "quoted-printable": + await self._writer.write(binascii.b2a_qp(chunk)) + else: + await self._writer.write(chunk) diff --git a/lib/aiohttp/payload.py b/lib/aiohttp/payload.py new file mode 100644 index 0000000..625b2ea --- /dev/null +++ b/lib/aiohttp/payload.py @@ -0,0 +1,465 @@ +import asyncio +import enum +import io +import json +import mimetypes +import os +import warnings +from abc import ABC, abstractmethod +from itertools import chain +from typing import ( + IO, + TYPE_CHECKING, + Any, + ByteString, + Dict, + Iterable, + Optional, + TextIO, + Tuple, + Type, + Union, +) + +from multidict import CIMultiDict + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import ( + PY_36, + content_disposition_header, + guess_filename, + parse_mimetype, + sentinel, +) +from .streams import StreamReader +from .typedefs import Final, JSONEncoder, _CIMultiDict + +__all__ = ( + "PAYLOAD_REGISTRY", + "get_payload", + "payload_type", + "Payload", + "BytesPayload", + "StringPayload", + "IOBasePayload", + "BytesIOPayload", + "BufferedReaderPayload", + "TextIOPayload", + "StringIOPayload", + "JsonPayload", + "AsyncIterablePayload", +) + +TOO_LARGE_BYTES_BODY: Final[int] = 2**20 # 1 MB + +if TYPE_CHECKING: # pragma: no cover + from typing import List + + +class LookupError(Exception): + pass + + +class Order(str, enum.Enum): + normal = "normal" + try_first = "try_first" + try_last = "try_last" + + +def get_payload(data: Any, *args: Any, **kwargs: Any) -> "Payload": + return PAYLOAD_REGISTRY.get(data, *args, **kwargs) + + +def register_payload( + factory: Type["Payload"], type: Any, *, order: Order = Order.normal +) -> None: + PAYLOAD_REGISTRY.register(factory, type, order=order) + + +class payload_type: + def __init__(self, type: Any, *, order: Order = Order.normal) -> None: + self.type = type + self.order = order + + def __call__(self, factory: Type["Payload"]) -> Type["Payload"]: + register_payload(factory, self.type, order=self.order) + return factory + + +PayloadType = Type["Payload"] +_PayloadRegistryItem = Tuple[PayloadType, Any] + + +class PayloadRegistry: + """Payload registry. + + note: we need zope.interface for more efficient adapter search + """ + + def __init__(self) -> None: + self._first: List[_PayloadRegistryItem] = [] + self._normal: List[_PayloadRegistryItem] = [] + self._last: List[_PayloadRegistryItem] = [] + + def get( + self, + data: Any, + *args: Any, + _CHAIN: "Type[chain[_PayloadRegistryItem]]" = chain, + **kwargs: Any, + ) -> "Payload": + if isinstance(data, Payload): + return data + for factory, type in _CHAIN(self._first, self._normal, self._last): + if isinstance(data, type): + return factory(data, *args, **kwargs) + + raise LookupError() + + def register( + self, factory: PayloadType, type: Any, *, order: Order = Order.normal + ) -> None: + if order is Order.try_first: + self._first.append((factory, type)) + elif order is Order.normal: + self._normal.append((factory, type)) + elif order is Order.try_last: + self._last.append((factory, type)) + else: + raise ValueError(f"Unsupported order {order!r}") + + +class Payload(ABC): + + _default_content_type: str = "application/octet-stream" + _size: Optional[int] = None + + def __init__( + self, + value: Any, + headers: Optional[ + Union[_CIMultiDict, Dict[str, str], Iterable[Tuple[str, str]]] + ] = None, + content_type: Optional[str] = sentinel, + filename: Optional[str] = None, + encoding: Optional[str] = None, + **kwargs: Any, + ) -> None: + self._encoding = encoding + self._filename = filename + self._headers: _CIMultiDict = CIMultiDict() + self._value = value + if content_type is not sentinel and content_type is not None: + self._headers[hdrs.CONTENT_TYPE] = content_type + elif self._filename is not None: + content_type = mimetypes.guess_type(self._filename)[0] + if content_type is None: + content_type = self._default_content_type + self._headers[hdrs.CONTENT_TYPE] = content_type + else: + self._headers[hdrs.CONTENT_TYPE] = self._default_content_type + self._headers.update(headers or {}) + + @property + def size(self) -> Optional[int]: + """Size of the payload.""" + return self._size + + @property + def filename(self) -> Optional[str]: + """Filename of the payload.""" + return self._filename + + @property + def headers(self) -> _CIMultiDict: + """Custom item headers""" + return self._headers + + @property + def _binary_headers(self) -> bytes: + return ( + "".join([k + ": " + v + "\r\n" for k, v in self.headers.items()]).encode( + "utf-8" + ) + + b"\r\n" + ) + + @property + def encoding(self) -> Optional[str]: + """Payload encoding""" + return self._encoding + + @property + def content_type(self) -> str: + """Content type""" + return self._headers[hdrs.CONTENT_TYPE] + + def set_content_disposition( + self, + disptype: str, + quote_fields: bool = True, + _charset: str = "utf-8", + **params: Any, + ) -> None: + """Sets ``Content-Disposition`` header.""" + self._headers[hdrs.CONTENT_DISPOSITION] = content_disposition_header( + disptype, quote_fields=quote_fields, _charset=_charset, **params + ) + + @abstractmethod + async def write(self, writer: AbstractStreamWriter) -> None: + """Write payload. + + writer is an AbstractStreamWriter instance: + """ + + +class BytesPayload(Payload): + def __init__(self, value: ByteString, *args: Any, **kwargs: Any) -> None: + if not isinstance(value, (bytes, bytearray, memoryview)): + raise TypeError(f"value argument must be byte-ish, not {type(value)!r}") + + if "content_type" not in kwargs: + kwargs["content_type"] = "application/octet-stream" + + super().__init__(value, *args, **kwargs) + + if isinstance(value, memoryview): + self._size = value.nbytes + else: + self._size = len(value) + + if self._size > TOO_LARGE_BYTES_BODY: + if PY_36: + kwargs = {"source": self} + else: + kwargs = {} + warnings.warn( + "Sending a large body directly with raw bytes might" + " lock the event loop. You should probably pass an " + "io.BytesIO object instead", + ResourceWarning, + **kwargs, + ) + + async def write(self, writer: AbstractStreamWriter) -> None: + await writer.write(self._value) + + +class StringPayload(BytesPayload): + def __init__( + self, + value: str, + *args: Any, + encoding: Optional[str] = None, + content_type: Optional[str] = None, + **kwargs: Any, + ) -> None: + + if encoding is None: + if content_type is None: + real_encoding = "utf-8" + content_type = "text/plain; charset=utf-8" + else: + mimetype = parse_mimetype(content_type) + real_encoding = mimetype.parameters.get("charset", "utf-8") + else: + if content_type is None: + content_type = "text/plain; charset=%s" % encoding + real_encoding = encoding + + super().__init__( + value.encode(real_encoding), + encoding=real_encoding, + content_type=content_type, + *args, + **kwargs, + ) + + +class StringIOPayload(StringPayload): + def __init__(self, value: IO[str], *args: Any, **kwargs: Any) -> None: + super().__init__(value.read(), *args, **kwargs) + + +class IOBasePayload(Payload): + _value: IO[Any] + + def __init__( + self, value: IO[Any], disposition: str = "attachment", *args: Any, **kwargs: Any + ) -> None: + if "filename" not in kwargs: + kwargs["filename"] = guess_filename(value) + + super().__init__(value, *args, **kwargs) + + if self._filename is not None and disposition is not None: + if hdrs.CONTENT_DISPOSITION not in self.headers: + self.set_content_disposition(disposition, filename=self._filename) + + async def write(self, writer: AbstractStreamWriter) -> None: + loop = asyncio.get_event_loop() + try: + chunk = await loop.run_in_executor(None, self._value.read, 2**16) + while chunk: + await writer.write(chunk) + chunk = await loop.run_in_executor(None, self._value.read, 2**16) + finally: + await loop.run_in_executor(None, self._value.close) + + +class TextIOPayload(IOBasePayload): + _value: TextIO + + def __init__( + self, + value: TextIO, + *args: Any, + encoding: Optional[str] = None, + content_type: Optional[str] = None, + **kwargs: Any, + ) -> None: + + if encoding is None: + if content_type is None: + encoding = "utf-8" + content_type = "text/plain; charset=utf-8" + else: + mimetype = parse_mimetype(content_type) + encoding = mimetype.parameters.get("charset", "utf-8") + else: + if content_type is None: + content_type = "text/plain; charset=%s" % encoding + + super().__init__( + value, + content_type=content_type, + encoding=encoding, + *args, + **kwargs, + ) + + @property + def size(self) -> Optional[int]: + try: + return os.fstat(self._value.fileno()).st_size - self._value.tell() + except OSError: + return None + + async def write(self, writer: AbstractStreamWriter) -> None: + loop = asyncio.get_event_loop() + try: + chunk = await loop.run_in_executor(None, self._value.read, 2**16) + while chunk: + data = ( + chunk.encode(encoding=self._encoding) + if self._encoding + else chunk.encode() + ) + await writer.write(data) + chunk = await loop.run_in_executor(None, self._value.read, 2**16) + finally: + await loop.run_in_executor(None, self._value.close) + + +class BytesIOPayload(IOBasePayload): + @property + def size(self) -> int: + position = self._value.tell() + end = self._value.seek(0, os.SEEK_END) + self._value.seek(position) + return end - position + + +class BufferedReaderPayload(IOBasePayload): + @property + def size(self) -> Optional[int]: + try: + return os.fstat(self._value.fileno()).st_size - self._value.tell() + except OSError: + # data.fileno() is not supported, e.g. + # io.BufferedReader(io.BytesIO(b'data')) + return None + + +class JsonPayload(BytesPayload): + def __init__( + self, + value: Any, + encoding: str = "utf-8", + content_type: str = "application/json", + dumps: JSONEncoder = json.dumps, + *args: Any, + **kwargs: Any, + ) -> None: + + super().__init__( + dumps(value).encode(encoding), + content_type=content_type, + encoding=encoding, + *args, + **kwargs, + ) + + +if TYPE_CHECKING: # pragma: no cover + from typing import AsyncIterable, AsyncIterator + + _AsyncIterator = AsyncIterator[bytes] + _AsyncIterable = AsyncIterable[bytes] +else: + from collections.abc import AsyncIterable, AsyncIterator + + _AsyncIterator = AsyncIterator + _AsyncIterable = AsyncIterable + + +class AsyncIterablePayload(Payload): + + _iter: Optional[_AsyncIterator] = None + + def __init__(self, value: _AsyncIterable, *args: Any, **kwargs: Any) -> None: + if not isinstance(value, AsyncIterable): + raise TypeError( + "value argument must support " + "collections.abc.AsyncIterablebe interface, " + "got {!r}".format(type(value)) + ) + + if "content_type" not in kwargs: + kwargs["content_type"] = "application/octet-stream" + + super().__init__(value, *args, **kwargs) + + self._iter = value.__aiter__() + + async def write(self, writer: AbstractStreamWriter) -> None: + if self._iter: + try: + # iter is not None check prevents rare cases + # when the case iterable is used twice + while True: + chunk = await self._iter.__anext__() + await writer.write(chunk) + except StopAsyncIteration: + self._iter = None + + +class StreamReaderPayload(AsyncIterablePayload): + def __init__(self, value: StreamReader, *args: Any, **kwargs: Any) -> None: + super().__init__(value.iter_any(), *args, **kwargs) + + +PAYLOAD_REGISTRY = PayloadRegistry() +PAYLOAD_REGISTRY.register(BytesPayload, (bytes, bytearray, memoryview)) +PAYLOAD_REGISTRY.register(StringPayload, str) +PAYLOAD_REGISTRY.register(StringIOPayload, io.StringIO) +PAYLOAD_REGISTRY.register(TextIOPayload, io.TextIOBase) +PAYLOAD_REGISTRY.register(BytesIOPayload, io.BytesIO) +PAYLOAD_REGISTRY.register(BufferedReaderPayload, (io.BufferedReader, io.BufferedRandom)) +PAYLOAD_REGISTRY.register(IOBasePayload, io.IOBase) +PAYLOAD_REGISTRY.register(StreamReaderPayload, StreamReader) +# try_last for giving a chance to more specialized async interables like +# multidict.BodyPartReaderPayload override the default +PAYLOAD_REGISTRY.register(AsyncIterablePayload, AsyncIterable, order=Order.try_last) diff --git a/lib/aiohttp/payload_streamer.py b/lib/aiohttp/payload_streamer.py new file mode 100644 index 0000000..9f8b8bc --- /dev/null +++ b/lib/aiohttp/payload_streamer.py @@ -0,0 +1,75 @@ +""" +Payload implemenation for coroutines as data provider. + +As a simple case, you can upload data from file:: + + @aiohttp.streamer + async def file_sender(writer, file_name=None): + with open(file_name, 'rb') as f: + chunk = f.read(2**16) + while chunk: + await writer.write(chunk) + + chunk = f.read(2**16) + +Then you can use `file_sender` like this: + + async with session.post('http://httpbin.org/post', + data=file_sender(file_name='huge_file')) as resp: + print(await resp.text()) + +..note:: Coroutine must accept `writer` as first argument + +""" + +import types +import warnings +from typing import Any, Awaitable, Callable, Dict, Tuple + +from .abc import AbstractStreamWriter +from .payload import Payload, payload_type + +__all__ = ("streamer",) + + +class _stream_wrapper: + def __init__( + self, + coro: Callable[..., Awaitable[None]], + args: Tuple[Any, ...], + kwargs: Dict[str, Any], + ) -> None: + self.coro = types.coroutine(coro) + self.args = args + self.kwargs = kwargs + + async def __call__(self, writer: AbstractStreamWriter) -> None: + await self.coro(writer, *self.args, **self.kwargs) # type: ignore[operator] + + +class streamer: + def __init__(self, coro: Callable[..., Awaitable[None]]) -> None: + warnings.warn( + "@streamer is deprecated, use async generators instead", + DeprecationWarning, + stacklevel=2, + ) + self.coro = coro + + def __call__(self, *args: Any, **kwargs: Any) -> _stream_wrapper: + return _stream_wrapper(self.coro, args, kwargs) + + +@payload_type(_stream_wrapper) +class StreamWrapperPayload(Payload): + async def write(self, writer: AbstractStreamWriter) -> None: + await self._value(writer) + + +@payload_type(streamer) +class StreamPayload(StreamWrapperPayload): + def __init__(self, value: Any, *args: Any, **kwargs: Any) -> None: + super().__init__(value(), *args, **kwargs) + + async def write(self, writer: AbstractStreamWriter) -> None: + await self._value(writer) diff --git a/lib/aiohttp/py.typed b/lib/aiohttp/py.typed new file mode 100644 index 0000000..f5642f7 --- /dev/null +++ b/lib/aiohttp/py.typed @@ -0,0 +1 @@ +Marker diff --git a/lib/aiohttp/pytest_plugin.py b/lib/aiohttp/pytest_plugin.py new file mode 100644 index 0000000..dd9a9f6 --- /dev/null +++ b/lib/aiohttp/pytest_plugin.py @@ -0,0 +1,391 @@ +import asyncio +import contextlib +import warnings +from collections.abc import Callable +from typing import Any, Awaitable, Callable, Dict, Generator, Optional, Union + +import pytest + +from aiohttp.helpers import PY_37, isasyncgenfunction +from aiohttp.web import Application + +from .test_utils import ( + BaseTestServer, + RawTestServer, + TestClient, + TestServer, + loop_context, + setup_test_loop, + teardown_test_loop, + unused_port as _unused_port, +) + +try: + import uvloop +except ImportError: # pragma: no cover + uvloop = None + +try: + import tokio +except ImportError: # pragma: no cover + tokio = None + +AiohttpClient = Callable[[Union[Application, BaseTestServer]], Awaitable[TestClient]] + + +def pytest_addoption(parser): # type: ignore[no-untyped-def] + parser.addoption( + "--aiohttp-fast", + action="store_true", + default=False, + help="run tests faster by disabling extra checks", + ) + parser.addoption( + "--aiohttp-loop", + action="store", + default="pyloop", + help="run tests with specific loop: pyloop, uvloop, tokio or all", + ) + parser.addoption( + "--aiohttp-enable-loop-debug", + action="store_true", + default=False, + help="enable event loop debug mode", + ) + + +def pytest_fixture_setup(fixturedef): # type: ignore[no-untyped-def] + """Set up pytest fixture. + + Allow fixtures to be coroutines. Run coroutine fixtures in an event loop. + """ + func = fixturedef.func + + if isasyncgenfunction(func): + # async generator fixture + is_async_gen = True + elif asyncio.iscoroutinefunction(func): + # regular async fixture + is_async_gen = False + else: + # not an async fixture, nothing to do + return + + strip_request = False + if "request" not in fixturedef.argnames: + fixturedef.argnames += ("request",) + strip_request = True + + def wrapper(*args, **kwargs): # type: ignore[no-untyped-def] + request = kwargs["request"] + if strip_request: + del kwargs["request"] + + # if neither the fixture nor the test use the 'loop' fixture, + # 'getfixturevalue' will fail because the test is not parameterized + # (this can be removed someday if 'loop' is no longer parameterized) + if "loop" not in request.fixturenames: + raise Exception( + "Asynchronous fixtures must depend on the 'loop' fixture or " + "be used in tests depending from it." + ) + + _loop = request.getfixturevalue("loop") + + if is_async_gen: + # for async generators, we need to advance the generator once, + # then advance it again in a finalizer + gen = func(*args, **kwargs) + + def finalizer(): # type: ignore[no-untyped-def] + try: + return _loop.run_until_complete(gen.__anext__()) + except StopAsyncIteration: + pass + + request.addfinalizer(finalizer) + return _loop.run_until_complete(gen.__anext__()) + else: + return _loop.run_until_complete(func(*args, **kwargs)) + + fixturedef.func = wrapper + + +@pytest.fixture +def fast(request): # type: ignore[no-untyped-def] + """--fast config option""" + return request.config.getoption("--aiohttp-fast") + + +@pytest.fixture +def loop_debug(request): # type: ignore[no-untyped-def] + """--enable-loop-debug config option""" + return request.config.getoption("--aiohttp-enable-loop-debug") + + +@contextlib.contextmanager +def _runtime_warning_context(): # type: ignore[no-untyped-def] + """Context manager which checks for RuntimeWarnings. + + This exists specifically to + avoid "coroutine 'X' was never awaited" warnings being missed. + + If RuntimeWarnings occur in the context a RuntimeError is raised. + """ + with warnings.catch_warnings(record=True) as _warnings: + yield + rw = [ + "{w.filename}:{w.lineno}:{w.message}".format(w=w) + for w in _warnings + if w.category == RuntimeWarning + ] + if rw: + raise RuntimeError( + "{} Runtime Warning{},\n{}".format( + len(rw), "" if len(rw) == 1 else "s", "\n".join(rw) + ) + ) + + +@contextlib.contextmanager +def _passthrough_loop_context(loop, fast=False): # type: ignore[no-untyped-def] + """Passthrough loop context. + + Sets up and tears down a loop unless one is passed in via the loop + argument when it's passed straight through. + """ + if loop: + # loop already exists, pass it straight through + yield loop + else: + # this shadows loop_context's standard behavior + loop = setup_test_loop() + yield loop + teardown_test_loop(loop, fast=fast) + + +def pytest_pycollect_makeitem(collector, name, obj): # type: ignore[no-untyped-def] + """Fix pytest collecting for coroutines.""" + if collector.funcnamefilter(name) and asyncio.iscoroutinefunction(obj): + return list(collector._genfunctions(name, obj)) + + +def pytest_pyfunc_call(pyfuncitem): # type: ignore[no-untyped-def] + """Run coroutines in an event loop instead of a normal function call.""" + fast = pyfuncitem.config.getoption("--aiohttp-fast") + if asyncio.iscoroutinefunction(pyfuncitem.function): + existing_loop = pyfuncitem.funcargs.get( + "proactor_loop" + ) or pyfuncitem.funcargs.get("loop", None) + with _runtime_warning_context(): + with _passthrough_loop_context(existing_loop, fast=fast) as _loop: + testargs = { + arg: pyfuncitem.funcargs[arg] + for arg in pyfuncitem._fixtureinfo.argnames + } + _loop.run_until_complete(pyfuncitem.obj(**testargs)) + + return True + + +def pytest_generate_tests(metafunc): # type: ignore[no-untyped-def] + if "loop_factory" not in metafunc.fixturenames: + return + + loops = metafunc.config.option.aiohttp_loop + avail_factories = {"pyloop": asyncio.DefaultEventLoopPolicy} + + if uvloop is not None: # pragma: no cover + avail_factories["uvloop"] = uvloop.EventLoopPolicy + + if tokio is not None: # pragma: no cover + avail_factories["tokio"] = tokio.EventLoopPolicy + + if loops == "all": + loops = "pyloop,uvloop?,tokio?" + + factories = {} # type: ignore[var-annotated] + for name in loops.split(","): + required = not name.endswith("?") + name = name.strip(" ?") + if name not in avail_factories: # pragma: no cover + if required: + raise ValueError( + "Unknown loop '%s', available loops: %s" + % (name, list(factories.keys())) + ) + else: + continue + factories[name] = avail_factories[name] + metafunc.parametrize( + "loop_factory", list(factories.values()), ids=list(factories.keys()) + ) + + +@pytest.fixture +def loop(loop_factory, fast, loop_debug): # type: ignore[no-untyped-def] + """Return an instance of the event loop.""" + policy = loop_factory() + asyncio.set_event_loop_policy(policy) + with loop_context(fast=fast) as _loop: + if loop_debug: + _loop.set_debug(True) # pragma: no cover + asyncio.set_event_loop(_loop) + yield _loop + + +@pytest.fixture +def proactor_loop(): # type: ignore[no-untyped-def] + if not PY_37: + policy = asyncio.get_event_loop_policy() + policy._loop_factory = asyncio.ProactorEventLoop # type: ignore[attr-defined] + else: + policy = asyncio.WindowsProactorEventLoopPolicy() # type: ignore[attr-defined] + asyncio.set_event_loop_policy(policy) + + with loop_context(policy.new_event_loop) as _loop: + asyncio.set_event_loop(_loop) + yield _loop + + +@pytest.fixture +def unused_port(aiohttp_unused_port): # type: ignore[no-untyped-def] # pragma: no cover + warnings.warn( + "Deprecated, use aiohttp_unused_port fixture instead", + DeprecationWarning, + stacklevel=2, + ) + return aiohttp_unused_port + + +@pytest.fixture +def aiohttp_unused_port(): # type: ignore[no-untyped-def] + """Return a port that is unused on the current host.""" + return _unused_port + + +@pytest.fixture +def aiohttp_server(loop): # type: ignore[no-untyped-def] + """Factory to create a TestServer instance, given an app. + + aiohttp_server(app, **kwargs) + """ + servers = [] + + async def go(app, *, port=None, **kwargs): # type: ignore[no-untyped-def] + server = TestServer(app, port=port) + await server.start_server(loop=loop, **kwargs) + servers.append(server) + return server + + yield go + + async def finalize() -> None: + while servers: + await servers.pop().close() + + loop.run_until_complete(finalize()) + + +@pytest.fixture +def test_server(aiohttp_server): # type: ignore[no-untyped-def] # pragma: no cover + warnings.warn( + "Deprecated, use aiohttp_server fixture instead", + DeprecationWarning, + stacklevel=2, + ) + return aiohttp_server + + +@pytest.fixture +def aiohttp_raw_server(loop): # type: ignore[no-untyped-def] + """Factory to create a RawTestServer instance, given a web handler. + + aiohttp_raw_server(handler, **kwargs) + """ + servers = [] + + async def go(handler, *, port=None, **kwargs): # type: ignore[no-untyped-def] + server = RawTestServer(handler, port=port) + await server.start_server(loop=loop, **kwargs) + servers.append(server) + return server + + yield go + + async def finalize() -> None: + while servers: + await servers.pop().close() + + loop.run_until_complete(finalize()) + + +@pytest.fixture +def raw_test_server( # type: ignore[no-untyped-def] # pragma: no cover + aiohttp_raw_server, +): + warnings.warn( + "Deprecated, use aiohttp_raw_server fixture instead", + DeprecationWarning, + stacklevel=2, + ) + return aiohttp_raw_server + + +@pytest.fixture +def aiohttp_client( + loop: asyncio.AbstractEventLoop, +) -> Generator[AiohttpClient, None, None]: + """Factory to create a TestClient instance. + + aiohttp_client(app, **kwargs) + aiohttp_client(server, **kwargs) + aiohttp_client(raw_server, **kwargs) + """ + clients = [] + + async def go( + __param: Union[Application, BaseTestServer], + *args: Any, + server_kwargs: Optional[Dict[str, Any]] = None, + **kwargs: Any + ) -> TestClient: + + if isinstance(__param, Callable) and not isinstance( # type: ignore[arg-type] + __param, (Application, BaseTestServer) + ): + __param = __param(loop, *args, **kwargs) + kwargs = {} + else: + assert not args, "args should be empty" + + if isinstance(__param, Application): + server_kwargs = server_kwargs or {} + server = TestServer(__param, loop=loop, **server_kwargs) + client = TestClient(server, loop=loop, **kwargs) + elif isinstance(__param, BaseTestServer): + client = TestClient(__param, loop=loop, **kwargs) + else: + raise ValueError("Unknown argument type: %r" % type(__param)) + + await client.start_server() + clients.append(client) + return client + + yield go + + async def finalize() -> None: + while clients: + await clients.pop().close() + + loop.run_until_complete(finalize()) + + +@pytest.fixture +def test_client(aiohttp_client): # type: ignore[no-untyped-def] # pragma: no cover + warnings.warn( + "Deprecated, use aiohttp_client fixture instead", + DeprecationWarning, + stacklevel=2, + ) + return aiohttp_client diff --git a/lib/aiohttp/resolver.py b/lib/aiohttp/resolver.py new file mode 100644 index 0000000..531ce93 --- /dev/null +++ b/lib/aiohttp/resolver.py @@ -0,0 +1,160 @@ +import asyncio +import socket +from typing import Any, Dict, List, Optional, Type, Union + +from .abc import AbstractResolver +from .helpers import get_running_loop + +__all__ = ("ThreadedResolver", "AsyncResolver", "DefaultResolver") + +try: + import aiodns + + # aiodns_default = hasattr(aiodns.DNSResolver, 'gethostbyname') +except ImportError: # pragma: no cover + aiodns = None + +aiodns_default = False + + +class ThreadedResolver(AbstractResolver): + """Threaded resolver. + + Uses an Executor for synchronous getaddrinfo() calls. + concurrent.futures.ThreadPoolExecutor is used by default. + """ + + def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: + self._loop = get_running_loop(loop) + + async def resolve( + self, hostname: str, port: int = 0, family: int = socket.AF_INET + ) -> List[Dict[str, Any]]: + infos = await self._loop.getaddrinfo( + hostname, + port, + type=socket.SOCK_STREAM, + family=family, + flags=socket.AI_ADDRCONFIG, + ) + + hosts = [] + for family, _, proto, _, address in infos: + if family == socket.AF_INET6: + if len(address) < 3: + # IPv6 is not supported by Python build, + # or IPv6 is not enabled in the host + continue + if address[3]: # type: ignore[misc] + # This is essential for link-local IPv6 addresses. + # LL IPv6 is a VERY rare case. Strictly speaking, we should use + # getnameinfo() unconditionally, but performance makes sense. + host, _port = socket.getnameinfo( + address, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV + ) + port = int(_port) + else: + host, port = address[:2] + else: # IPv4 + assert family == socket.AF_INET + host, port = address # type: ignore[misc] + hosts.append( + { + "hostname": hostname, + "host": host, + "port": port, + "family": family, + "proto": proto, + "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV, + } + ) + + return hosts + + async def close(self) -> None: + pass + + +class AsyncResolver(AbstractResolver): + """Use the `aiodns` package to make asynchronous DNS lookups""" + + def __init__( + self, + loop: Optional[asyncio.AbstractEventLoop] = None, + *args: Any, + **kwargs: Any + ) -> None: + if aiodns is None: + raise RuntimeError("Resolver requires aiodns library") + + self._loop = get_running_loop(loop) + self._resolver = aiodns.DNSResolver(*args, loop=loop, **kwargs) + + if not hasattr(self._resolver, "gethostbyname"): + # aiodns 1.1 is not available, fallback to DNSResolver.query + self.resolve = self._resolve_with_query # type: ignore + + async def resolve( + self, host: str, port: int = 0, family: int = socket.AF_INET + ) -> List[Dict[str, Any]]: + try: + resp = await self._resolver.gethostbyname(host, family) + except aiodns.error.DNSError as exc: + msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" + raise OSError(msg) from exc + hosts = [] + for address in resp.addresses: + hosts.append( + { + "hostname": host, + "host": address, + "port": port, + "family": family, + "proto": 0, + "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV, + } + ) + + if not hosts: + raise OSError("DNS lookup failed") + + return hosts + + async def _resolve_with_query( + self, host: str, port: int = 0, family: int = socket.AF_INET + ) -> List[Dict[str, Any]]: + if family == socket.AF_INET6: + qtype = "AAAA" + else: + qtype = "A" + + try: + resp = await self._resolver.query(host, qtype) + except aiodns.error.DNSError as exc: + msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" + raise OSError(msg) from exc + + hosts = [] + for rr in resp: + hosts.append( + { + "hostname": host, + "host": rr.host, + "port": port, + "family": family, + "proto": 0, + "flags": socket.AI_NUMERICHOST, + } + ) + + if not hosts: + raise OSError("DNS lookup failed") + + return hosts + + async def close(self) -> None: + self._resolver.cancel() + + +_DefaultType = Type[Union[AsyncResolver, ThreadedResolver]] +DefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver diff --git a/lib/aiohttp/streams.py b/lib/aiohttp/streams.py new file mode 100644 index 0000000..726b023 --- /dev/null +++ b/lib/aiohttp/streams.py @@ -0,0 +1,660 @@ +import asyncio +import collections +import warnings +from typing import Awaitable, Callable, Deque, Generic, List, Optional, Tuple, TypeVar + +from .base_protocol import BaseProtocol +from .helpers import BaseTimerContext, set_exception, set_result +from .log import internal_logger +from .typedefs import Final + +__all__ = ( + "EMPTY_PAYLOAD", + "EofStream", + "StreamReader", + "DataQueue", + "FlowControlDataQueue", +) + +_T = TypeVar("_T") + + +class EofStream(Exception): + """eof stream indication.""" + + +class AsyncStreamIterator(Generic[_T]): + def __init__(self, read_func: Callable[[], Awaitable[_T]]) -> None: + self.read_func = read_func + + def __aiter__(self) -> "AsyncStreamIterator[_T]": + return self + + async def __anext__(self) -> _T: + try: + rv = await self.read_func() + except EofStream: + raise StopAsyncIteration + if rv == b"": + raise StopAsyncIteration + return rv + + +class ChunkTupleAsyncStreamIterator: + def __init__(self, stream: "StreamReader") -> None: + self._stream = stream + + def __aiter__(self) -> "ChunkTupleAsyncStreamIterator": + return self + + async def __anext__(self) -> Tuple[bytes, bool]: + rv = await self._stream.readchunk() + if rv == (b"", False): + raise StopAsyncIteration + return rv + + +class AsyncStreamReaderMixin: + def __aiter__(self) -> AsyncStreamIterator[bytes]: + return AsyncStreamIterator(self.readline) # type: ignore[attr-defined] + + def iter_chunked(self, n: int) -> AsyncStreamIterator[bytes]: + """Returns an asynchronous iterator that yields chunks of size n. + + Python-3.5 available for Python 3.5+ only + """ + return AsyncStreamIterator( + lambda: self.read(n) # type: ignore[attr-defined,no-any-return] + ) + + def iter_any(self) -> AsyncStreamIterator[bytes]: + """Yield all available data as soon as it is received. + + Python-3.5 available for Python 3.5+ only + """ + return AsyncStreamIterator(self.readany) # type: ignore[attr-defined] + + def iter_chunks(self) -> ChunkTupleAsyncStreamIterator: + """Yield chunks of data as they are received by the server. + + The yielded objects are tuples + of (bytes, bool) as returned by the StreamReader.readchunk method. + + Python-3.5 available for Python 3.5+ only + """ + return ChunkTupleAsyncStreamIterator(self) # type: ignore[arg-type] + + +class StreamReader(AsyncStreamReaderMixin): + """An enhancement of asyncio.StreamReader. + + Supports asynchronous iteration by line, chunk or as available:: + + async for line in reader: + ... + async for chunk in reader.iter_chunked(1024): + ... + async for slice in reader.iter_any(): + ... + + """ + + total_bytes = 0 + + def __init__( + self, + protocol: BaseProtocol, + limit: int, + *, + timer: Optional[BaseTimerContext] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + self._protocol = protocol + self._low_water = limit + self._high_water = limit * 2 + if loop is None: + loop = asyncio.get_event_loop() + self._loop = loop + self._size = 0 + self._cursor = 0 + self._http_chunk_splits: Optional[List[int]] = None + self._buffer: Deque[bytes] = collections.deque() + self._buffer_offset = 0 + self._eof = False + self._waiter: Optional[asyncio.Future[None]] = None + self._eof_waiter: Optional[asyncio.Future[None]] = None + self._exception: Optional[BaseException] = None + self._timer = timer + self._eof_callbacks: List[Callable[[], None]] = [] + + def __repr__(self) -> str: + info = [self.__class__.__name__] + if self._size: + info.append("%d bytes" % self._size) + if self._eof: + info.append("eof") + if self._low_water != 2**16: # default limit + info.append("low=%d high=%d" % (self._low_water, self._high_water)) + if self._waiter: + info.append("w=%r" % self._waiter) + if self._exception: + info.append("e=%r" % self._exception) + return "<%s>" % " ".join(info) + + def get_read_buffer_limits(self) -> Tuple[int, int]: + return (self._low_water, self._high_water) + + def exception(self) -> Optional[BaseException]: + return self._exception + + def set_exception(self, exc: BaseException) -> None: + self._exception = exc + self._eof_callbacks.clear() + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_exception(waiter, exc) + + waiter = self._eof_waiter + if waiter is not None: + self._eof_waiter = None + set_exception(waiter, exc) + + def on_eof(self, callback: Callable[[], None]) -> None: + if self._eof: + try: + callback() + except Exception: + internal_logger.exception("Exception in eof callback") + else: + self._eof_callbacks.append(callback) + + def feed_eof(self) -> None: + self._eof = True + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + waiter = self._eof_waiter + if waiter is not None: + self._eof_waiter = None + set_result(waiter, None) + + for cb in self._eof_callbacks: + try: + cb() + except Exception: + internal_logger.exception("Exception in eof callback") + + self._eof_callbacks.clear() + + def is_eof(self) -> bool: + """Return True if 'feed_eof' was called.""" + return self._eof + + def at_eof(self) -> bool: + """Return True if the buffer is empty and 'feed_eof' was called.""" + return self._eof and not self._buffer + + async def wait_eof(self) -> None: + if self._eof: + return + + assert self._eof_waiter is None + self._eof_waiter = self._loop.create_future() + try: + await self._eof_waiter + finally: + self._eof_waiter = None + + def unread_data(self, data: bytes) -> None: + """rollback reading some data from stream, inserting it to buffer head.""" + warnings.warn( + "unread_data() is deprecated " + "and will be removed in future releases (#3260)", + DeprecationWarning, + stacklevel=2, + ) + if not data: + return + + if self._buffer_offset: + self._buffer[0] = self._buffer[0][self._buffer_offset :] + self._buffer_offset = 0 + self._size += len(data) + self._cursor -= len(data) + self._buffer.appendleft(data) + self._eof_counter = 0 + + # TODO: size is ignored, remove the param later + def feed_data(self, data: bytes, size: int = 0) -> None: + assert not self._eof, "feed_data after feed_eof" + + if not data: + return + + self._size += len(data) + self._buffer.append(data) + self.total_bytes += len(data) + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + if self._size > self._high_water and not self._protocol._reading_paused: + self._protocol.pause_reading() + + def begin_http_chunk_receiving(self) -> None: + if self._http_chunk_splits is None: + if self.total_bytes: + raise RuntimeError( + "Called begin_http_chunk_receiving when" "some data was already fed" + ) + self._http_chunk_splits = [] + + def end_http_chunk_receiving(self) -> None: + if self._http_chunk_splits is None: + raise RuntimeError( + "Called end_chunk_receiving without calling " + "begin_chunk_receiving first" + ) + + # self._http_chunk_splits contains logical byte offsets from start of + # the body transfer. Each offset is the offset of the end of a chunk. + # "Logical" means bytes, accessible for a user. + # If no chunks containig logical data were received, current position + # is difinitely zero. + pos = self._http_chunk_splits[-1] if self._http_chunk_splits else 0 + + if self.total_bytes == pos: + # We should not add empty chunks here. So we check for that. + # Note, when chunked + gzip is used, we can receive a chunk + # of compressed data, but that data may not be enough for gzip FSM + # to yield any uncompressed data. That's why current position may + # not change after receiving a chunk. + return + + self._http_chunk_splits.append(self.total_bytes) + + # wake up readchunk when end of http chunk received + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + async def _wait(self, func_name: str) -> None: + # StreamReader uses a future to link the protocol feed_data() method + # to a read coroutine. Running two read coroutines at the same time + # would have an unexpected behaviour. It would not possible to know + # which coroutine would get the next data. + if self._waiter is not None: + raise RuntimeError( + "%s() called while another coroutine is " + "already waiting for incoming data" % func_name + ) + + waiter = self._waiter = self._loop.create_future() + try: + if self._timer: + with self._timer: + await waiter + else: + await waiter + finally: + self._waiter = None + + async def readline(self) -> bytes: + return await self.readuntil() + + async def readuntil(self, separator: bytes = b"\n") -> bytes: + seplen = len(separator) + if seplen == 0: + raise ValueError("Separator should be at least one-byte string") + + if self._exception is not None: + raise self._exception + + chunk = b"" + chunk_size = 0 + not_enough = True + + while not_enough: + while self._buffer and not_enough: + offset = self._buffer_offset + ichar = self._buffer[0].find(separator, offset) + 1 + # Read from current offset to found separator or to the end. + data = self._read_nowait_chunk(ichar - offset if ichar else -1) + chunk += data + chunk_size += len(data) + if ichar: + not_enough = False + + if chunk_size > self._high_water: + raise ValueError("Chunk too big") + + if self._eof: + break + + if not_enough: + await self._wait("readuntil") + + return chunk + + async def read(self, n: int = -1) -> bytes: + if self._exception is not None: + raise self._exception + + # migration problem; with DataQueue you have to catch + # EofStream exception, so common way is to run payload.read() inside + # infinite loop. what can cause real infinite loop with StreamReader + # lets keep this code one major release. + if __debug__: + if self._eof and not self._buffer: + self._eof_counter = getattr(self, "_eof_counter", 0) + 1 + if self._eof_counter > 5: + internal_logger.warning( + "Multiple access to StreamReader in eof state, " + "might be infinite loop.", + stack_info=True, + ) + + if not n: + return b"" + + if n < 0: + # This used to just loop creating a new waiter hoping to + # collect everything in self._buffer, but that would + # deadlock if the subprocess sends more than self.limit + # bytes. So just call self.readany() until EOF. + blocks = [] + while True: + block = await self.readany() + if not block: + break + blocks.append(block) + return b"".join(blocks) + + # TODO: should be `if` instead of `while` + # because waiter maybe triggered on chunk end, + # without feeding any data + while not self._buffer and not self._eof: + await self._wait("read") + + return self._read_nowait(n) + + async def readany(self) -> bytes: + if self._exception is not None: + raise self._exception + + # TODO: should be `if` instead of `while` + # because waiter maybe triggered on chunk end, + # without feeding any data + while not self._buffer and not self._eof: + await self._wait("readany") + + return self._read_nowait(-1) + + async def readchunk(self) -> Tuple[bytes, bool]: + """Returns a tuple of (data, end_of_http_chunk). + + When chunked transfer + encoding is used, end_of_http_chunk is a boolean indicating if the end + of the data corresponds to the end of a HTTP chunk , otherwise it is + always False. + """ + while True: + if self._exception is not None: + raise self._exception + + while self._http_chunk_splits: + pos = self._http_chunk_splits.pop(0) + if pos == self._cursor: + return (b"", True) + if pos > self._cursor: + return (self._read_nowait(pos - self._cursor), True) + internal_logger.warning( + "Skipping HTTP chunk end due to data " + "consumption beyond chunk boundary" + ) + + if self._buffer: + return (self._read_nowait_chunk(-1), False) + # return (self._read_nowait(-1), False) + + if self._eof: + # Special case for signifying EOF. + # (b'', True) is not a final return value actually. + return (b"", False) + + await self._wait("readchunk") + + async def readexactly(self, n: int) -> bytes: + if self._exception is not None: + raise self._exception + + blocks: List[bytes] = [] + while n > 0: + block = await self.read(n) + if not block: + partial = b"".join(blocks) + raise asyncio.IncompleteReadError(partial, len(partial) + n) + blocks.append(block) + n -= len(block) + + return b"".join(blocks) + + def read_nowait(self, n: int = -1) -> bytes: + # default was changed to be consistent with .read(-1) + # + # I believe the most users don't know about the method and + # they are not affected. + if self._exception is not None: + raise self._exception + + if self._waiter and not self._waiter.done(): + raise RuntimeError( + "Called while some coroutine is waiting for incoming data." + ) + + return self._read_nowait(n) + + def _read_nowait_chunk(self, n: int) -> bytes: + first_buffer = self._buffer[0] + offset = self._buffer_offset + if n != -1 and len(first_buffer) - offset > n: + data = first_buffer[offset : offset + n] + self._buffer_offset += n + + elif offset: + self._buffer.popleft() + data = first_buffer[offset:] + self._buffer_offset = 0 + + else: + data = self._buffer.popleft() + + self._size -= len(data) + self._cursor += len(data) + + chunk_splits = self._http_chunk_splits + # Prevent memory leak: drop useless chunk splits + while chunk_splits and chunk_splits[0] < self._cursor: + chunk_splits.pop(0) + + if self._size < self._low_water and self._protocol._reading_paused: + self._protocol.resume_reading() + return data + + def _read_nowait(self, n: int) -> bytes: + """Read not more than n bytes, or whole buffer if n == -1""" + chunks = [] + + while self._buffer: + chunk = self._read_nowait_chunk(n) + chunks.append(chunk) + if n != -1: + n -= len(chunk) + if n == 0: + break + + return b"".join(chunks) if chunks else b"" + + +class EmptyStreamReader(StreamReader): # lgtm [py/missing-call-to-init] + def __init__(self) -> None: + pass + + def exception(self) -> Optional[BaseException]: + return None + + def set_exception(self, exc: BaseException) -> None: + pass + + def on_eof(self, callback: Callable[[], None]) -> None: + try: + callback() + except Exception: + internal_logger.exception("Exception in eof callback") + + def feed_eof(self) -> None: + pass + + def is_eof(self) -> bool: + return True + + def at_eof(self) -> bool: + return True + + async def wait_eof(self) -> None: + return + + def feed_data(self, data: bytes, n: int = 0) -> None: + pass + + async def readline(self) -> bytes: + return b"" + + async def read(self, n: int = -1) -> bytes: + return b"" + + # TODO add async def readuntil + + async def readany(self) -> bytes: + return b"" + + async def readchunk(self) -> Tuple[bytes, bool]: + return (b"", True) + + async def readexactly(self, n: int) -> bytes: + raise asyncio.IncompleteReadError(b"", n) + + def read_nowait(self, n: int = -1) -> bytes: + return b"" + + +EMPTY_PAYLOAD: Final[StreamReader] = EmptyStreamReader() + + +class DataQueue(Generic[_T]): + """DataQueue is a general-purpose blocking queue with one reader.""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop = loop + self._eof = False + self._waiter: Optional[asyncio.Future[None]] = None + self._exception: Optional[BaseException] = None + self._size = 0 + self._buffer: Deque[Tuple[_T, int]] = collections.deque() + + def __len__(self) -> int: + return len(self._buffer) + + def is_eof(self) -> bool: + return self._eof + + def at_eof(self) -> bool: + return self._eof and not self._buffer + + def exception(self) -> Optional[BaseException]: + return self._exception + + def set_exception(self, exc: BaseException) -> None: + self._eof = True + self._exception = exc + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_exception(waiter, exc) + + def feed_data(self, data: _T, size: int = 0) -> None: + self._size += size + self._buffer.append((data, size)) + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + def feed_eof(self) -> None: + self._eof = True + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + async def read(self) -> _T: + if not self._buffer and not self._eof: + assert not self._waiter + self._waiter = self._loop.create_future() + try: + await self._waiter + except (asyncio.CancelledError, asyncio.TimeoutError): + self._waiter = None + raise + + if self._buffer: + data, size = self._buffer.popleft() + self._size -= size + return data + else: + if self._exception is not None: + raise self._exception + else: + raise EofStream + + def __aiter__(self) -> AsyncStreamIterator[_T]: + return AsyncStreamIterator(self.read) + + +class FlowControlDataQueue(DataQueue[_T]): + """FlowControlDataQueue resumes and pauses an underlying stream. + + It is a destination for parsed data. + """ + + def __init__( + self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop + ) -> None: + super().__init__(loop=loop) + + self._protocol = protocol + self._limit = limit * 2 + + def feed_data(self, data: _T, size: int = 0) -> None: + super().feed_data(data, size) + + if self._size > self._limit and not self._protocol._reading_paused: + self._protocol.pause_reading() + + async def read(self) -> _T: + try: + return await super().read() + finally: + if self._size < self._limit and self._protocol._reading_paused: + self._protocol.resume_reading() diff --git a/lib/aiohttp/tcp_helpers.py b/lib/aiohttp/tcp_helpers.py new file mode 100644 index 0000000..88b2442 --- /dev/null +++ b/lib/aiohttp/tcp_helpers.py @@ -0,0 +1,37 @@ +"""Helper methods to tune a TCP connection""" + +import asyncio +import socket +from contextlib import suppress +from typing import Optional # noqa + +__all__ = ("tcp_keepalive", "tcp_nodelay") + + +if hasattr(socket, "SO_KEEPALIVE"): + + def tcp_keepalive(transport: asyncio.Transport) -> None: + sock = transport.get_extra_info("socket") + if sock is not None: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + +else: + + def tcp_keepalive(transport: asyncio.Transport) -> None: # pragma: no cover + pass + + +def tcp_nodelay(transport: asyncio.Transport, value: bool) -> None: + sock = transport.get_extra_info("socket") + + if sock is None: + return + + if sock.family not in (socket.AF_INET, socket.AF_INET6): + return + + value = bool(value) + + # socket may be closed already, on windows OSError get raised + with suppress(OSError): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, value) diff --git a/lib/aiohttp/test_utils.py b/lib/aiohttp/test_utils.py new file mode 100644 index 0000000..fcda2f3 --- /dev/null +++ b/lib/aiohttp/test_utils.py @@ -0,0 +1,706 @@ +"""Utilities shared by tests.""" + +import asyncio +import contextlib +import gc +import inspect +import ipaddress +import os +import socket +import sys +import warnings +from abc import ABC, abstractmethod +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Iterator, + List, + Optional, + Type, + Union, + cast, +) +from unittest import mock + +from aiosignal import Signal +from multidict import CIMultiDict, CIMultiDictProxy +from yarl import URL + +import aiohttp +from aiohttp.client import _RequestContextManager, _WSRequestContextManager + +from . import ClientSession, hdrs +from .abc import AbstractCookieJar +from .client_reqrep import ClientResponse +from .client_ws import ClientWebSocketResponse +from .helpers import PY_38, sentinel +from .http import HttpVersion, RawRequestMessage +from .web import ( + Application, + AppRunner, + BaseRunner, + Request, + Server, + ServerRunner, + SockSite, + UrlMappingMatchInfo, +) +from .web_protocol import _RequestHandler + +if TYPE_CHECKING: # pragma: no cover + from ssl import SSLContext +else: + SSLContext = None + +if PY_38: + from unittest import IsolatedAsyncioTestCase as TestCase +else: + from asynctest import TestCase # type: ignore[no-redef] + +REUSE_ADDRESS = os.name == "posix" and sys.platform != "cygwin" + + +def get_unused_port_socket( + host: str, family: socket.AddressFamily = socket.AF_INET +) -> socket.socket: + return get_port_socket(host, 0, family) + + +def get_port_socket( + host: str, port: int, family: socket.AddressFamily +) -> socket.socket: + s = socket.socket(family, socket.SOCK_STREAM) + if REUSE_ADDRESS: + # Windows has different semantics for SO_REUSEADDR, + # so don't set it. Ref: + # https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((host, port)) + return s + + +def unused_port() -> int: + """Return a port that is unused on the current host.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", 0)) + return cast(int, s.getsockname()[1]) + + +class BaseTestServer(ABC): + __test__ = False + + def __init__( + self, + *, + scheme: Union[str, object] = sentinel, + loop: Optional[asyncio.AbstractEventLoop] = None, + host: str = "127.0.0.1", + port: Optional[int] = None, + skip_url_asserts: bool = False, + socket_factory: Callable[ + [str, int, socket.AddressFamily], socket.socket + ] = get_port_socket, + **kwargs: Any, + ) -> None: + self._loop = loop + self.runner: Optional[BaseRunner] = None + self._root: Optional[URL] = None + self.host = host + self.port = port + self._closed = False + self.scheme = scheme + self.skip_url_asserts = skip_url_asserts + self.socket_factory = socket_factory + + async def start_server( + self, loop: Optional[asyncio.AbstractEventLoop] = None, **kwargs: Any + ) -> None: + if self.runner: + return + self._loop = loop + self._ssl = kwargs.pop("ssl", None) + self.runner = await self._make_runner(**kwargs) + await self.runner.setup() + if not self.port: + self.port = 0 + try: + version = ipaddress.ip_address(self.host).version + except ValueError: + version = 4 + family = socket.AF_INET6 if version == 6 else socket.AF_INET + _sock = self.socket_factory(self.host, self.port, family) + self.host, self.port = _sock.getsockname()[:2] + site = SockSite(self.runner, sock=_sock, ssl_context=self._ssl) + await site.start() + server = site._server + assert server is not None + sockets = server.sockets + assert sockets is not None + self.port = sockets[0].getsockname()[1] + if self.scheme is sentinel: + if self._ssl: + scheme = "https" + else: + scheme = "http" + self.scheme = scheme + self._root = URL(f"{self.scheme}://{self.host}:{self.port}") + + @abstractmethod # pragma: no cover + async def _make_runner(self, **kwargs: Any) -> BaseRunner: + pass + + def make_url(self, path: str) -> URL: + assert self._root is not None + url = URL(path) + if not self.skip_url_asserts: + assert not url.is_absolute() + return self._root.join(url) + else: + return URL(str(self._root) + path) + + @property + def started(self) -> bool: + return self.runner is not None + + @property + def closed(self) -> bool: + return self._closed + + @property + def handler(self) -> Server: + # for backward compatibility + # web.Server instance + runner = self.runner + assert runner is not None + assert runner.server is not None + return runner.server + + async def close(self) -> None: + """Close all fixtures created by the test client. + + After that point, the TestClient is no longer usable. + + This is an idempotent function: running close multiple times + will not have any additional effects. + + close is also run when the object is garbage collected, and on + exit when used as a context manager. + + """ + if self.started and not self.closed: + assert self.runner is not None + await self.runner.cleanup() + self._root = None + self.port = None + self._closed = True + + def __enter__(self) -> None: + raise TypeError("Use async with instead") + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + # __exit__ should exist in pair with __enter__ but never executed + pass # pragma: no cover + + async def __aenter__(self) -> "BaseTestServer": + await self.start_server(loop=self._loop) + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + await self.close() + + +class TestServer(BaseTestServer): + def __init__( + self, + app: Application, + *, + scheme: Union[str, object] = sentinel, + host: str = "127.0.0.1", + port: Optional[int] = None, + **kwargs: Any, + ): + self.app = app + super().__init__(scheme=scheme, host=host, port=port, **kwargs) + + async def _make_runner(self, **kwargs: Any) -> BaseRunner: + return AppRunner(self.app, **kwargs) + + +class RawTestServer(BaseTestServer): + def __init__( + self, + handler: _RequestHandler, + *, + scheme: Union[str, object] = sentinel, + host: str = "127.0.0.1", + port: Optional[int] = None, + **kwargs: Any, + ) -> None: + self._handler = handler + super().__init__(scheme=scheme, host=host, port=port, **kwargs) + + async def _make_runner(self, debug: bool = True, **kwargs: Any) -> ServerRunner: + srv = Server(self._handler, loop=self._loop, debug=debug, **kwargs) + return ServerRunner(srv, debug=debug, **kwargs) + + +class TestClient: + """ + A test client implementation. + + To write functional tests for aiohttp based servers. + + """ + + __test__ = False + + def __init__( + self, + server: BaseTestServer, + *, + cookie_jar: Optional[AbstractCookieJar] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + **kwargs: Any, + ) -> None: + if not isinstance(server, BaseTestServer): + raise TypeError( + "server must be TestServer " "instance, found type: %r" % type(server) + ) + self._server = server + self._loop = loop + if cookie_jar is None: + cookie_jar = aiohttp.CookieJar(unsafe=True, loop=loop) + self._session = ClientSession(loop=loop, cookie_jar=cookie_jar, **kwargs) + self._closed = False + self._responses: List[ClientResponse] = [] + self._websockets: List[ClientWebSocketResponse] = [] + + async def start_server(self) -> None: + await self._server.start_server(loop=self._loop) + + @property + def host(self) -> str: + return self._server.host + + @property + def port(self) -> Optional[int]: + return self._server.port + + @property + def server(self) -> BaseTestServer: + return self._server + + @property + def app(self) -> Optional[Application]: + return cast(Optional[Application], getattr(self._server, "app", None)) + + @property + def session(self) -> ClientSession: + """An internal aiohttp.ClientSession. + + Unlike the methods on the TestClient, client session requests + do not automatically include the host in the url queried, and + will require an absolute path to the resource. + + """ + return self._session + + def make_url(self, path: str) -> URL: + return self._server.make_url(path) + + async def _request(self, method: str, path: str, **kwargs: Any) -> ClientResponse: + resp = await self._session.request(method, self.make_url(path), **kwargs) + # save it to close later + self._responses.append(resp) + return resp + + def request(self, method: str, path: str, **kwargs: Any) -> _RequestContextManager: + """Routes a request to tested http server. + + The interface is identical to aiohttp.ClientSession.request, + except the loop kwarg is overridden by the instance used by the + test server. + + """ + return _RequestContextManager(self._request(method, path, **kwargs)) + + def get(self, path: str, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP GET request.""" + return _RequestContextManager(self._request(hdrs.METH_GET, path, **kwargs)) + + def post(self, path: str, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP POST request.""" + return _RequestContextManager(self._request(hdrs.METH_POST, path, **kwargs)) + + def options(self, path: str, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP OPTIONS request.""" + return _RequestContextManager(self._request(hdrs.METH_OPTIONS, path, **kwargs)) + + def head(self, path: str, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP HEAD request.""" + return _RequestContextManager(self._request(hdrs.METH_HEAD, path, **kwargs)) + + def put(self, path: str, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PUT request.""" + return _RequestContextManager(self._request(hdrs.METH_PUT, path, **kwargs)) + + def patch(self, path: str, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PATCH request.""" + return _RequestContextManager(self._request(hdrs.METH_PATCH, path, **kwargs)) + + def delete(self, path: str, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PATCH request.""" + return _RequestContextManager(self._request(hdrs.METH_DELETE, path, **kwargs)) + + def ws_connect(self, path: str, **kwargs: Any) -> _WSRequestContextManager: + """Initiate websocket connection. + + The api corresponds to aiohttp.ClientSession.ws_connect. + + """ + return _WSRequestContextManager(self._ws_connect(path, **kwargs)) + + async def _ws_connect(self, path: str, **kwargs: Any) -> ClientWebSocketResponse: + ws = await self._session.ws_connect(self.make_url(path), **kwargs) + self._websockets.append(ws) + return ws + + async def close(self) -> None: + """Close all fixtures created by the test client. + + After that point, the TestClient is no longer usable. + + This is an idempotent function: running close multiple times + will not have any additional effects. + + close is also run on exit when used as a(n) (asynchronous) + context manager. + + """ + if not self._closed: + for resp in self._responses: + resp.close() + for ws in self._websockets: + await ws.close() + await self._session.close() + await self._server.close() + self._closed = True + + def __enter__(self) -> None: + raise TypeError("Use async with instead") + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + # __exit__ should exist in pair with __enter__ but never executed + pass # pragma: no cover + + async def __aenter__(self) -> "TestClient": + await self.start_server() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + await self.close() + + +class AioHTTPTestCase(TestCase): + """A base class to allow for unittest web applications using aiohttp. + + Provides the following: + + * self.client (aiohttp.test_utils.TestClient): an aiohttp test client. + * self.loop (asyncio.BaseEventLoop): the event loop in which the + application and server are running. + * self.app (aiohttp.web.Application): the application returned by + self.get_application() + + Note that the TestClient's methods are asynchronous: you have to + execute function on the test client using asynchronous methods. + """ + + async def get_application(self) -> Application: + """Get application. + + This method should be overridden + to return the aiohttp.web.Application + object to test. + """ + return self.get_app() + + def get_app(self) -> Application: + """Obsolete method used to constructing web application. + + Use .get_application() coroutine instead. + """ + raise RuntimeError("Did you forget to define get_application()?") + + def setUp(self) -> None: + if not PY_38: + asyncio.get_event_loop().run_until_complete(self.asyncSetUp()) + + async def asyncSetUp(self) -> None: + try: + self.loop = asyncio.get_running_loop() + except (AttributeError, RuntimeError): # AttributeError->py36 + self.loop = asyncio.get_event_loop_policy().get_event_loop() + + return await self.setUpAsync() + + async def setUpAsync(self) -> None: + self.app = await self.get_application() + self.server = await self.get_server(self.app) + self.client = await self.get_client(self.server) + + await self.client.start_server() + + def tearDown(self) -> None: + if not PY_38: + self.loop.run_until_complete(self.asyncTearDown()) + + async def asyncTearDown(self) -> None: + return await self.tearDownAsync() + + async def tearDownAsync(self) -> None: + await self.client.close() + + async def get_server(self, app: Application) -> TestServer: + """Return a TestServer instance.""" + return TestServer(app, loop=self.loop) + + async def get_client(self, server: TestServer) -> TestClient: + """Return a TestClient instance.""" + return TestClient(server, loop=self.loop) + + +def unittest_run_loop(func: Any, *args: Any, **kwargs: Any) -> Any: + """ + A decorator dedicated to use with asynchronous AioHTTPTestCase test methods. + + In 3.8+, this does nothing. + """ + warnings.warn( + "Decorator `@unittest_run_loop` is no longer needed in aiohttp 3.8+", + DeprecationWarning, + stacklevel=2, + ) + return func + + +_LOOP_FACTORY = Callable[[], asyncio.AbstractEventLoop] + + +@contextlib.contextmanager +def loop_context( + loop_factory: _LOOP_FACTORY = asyncio.new_event_loop, fast: bool = False +) -> Iterator[asyncio.AbstractEventLoop]: + """A contextmanager that creates an event_loop, for test purposes. + + Handles the creation and cleanup of a test loop. + """ + loop = setup_test_loop(loop_factory) + yield loop + teardown_test_loop(loop, fast=fast) + + +def setup_test_loop( + loop_factory: _LOOP_FACTORY = asyncio.new_event_loop, +) -> asyncio.AbstractEventLoop: + """Create and return an asyncio.BaseEventLoop instance. + + The caller should also call teardown_test_loop, + once they are done with the loop. + """ + loop = loop_factory() + try: + module = loop.__class__.__module__ + skip_watcher = "uvloop" in module + except AttributeError: # pragma: no cover + # Just in case + skip_watcher = True + asyncio.set_event_loop(loop) + if sys.platform != "win32" and not skip_watcher: + policy = asyncio.get_event_loop_policy() + watcher: asyncio.AbstractChildWatcher + try: # Python >= 3.8 + # Refs: + # * https://github.com/pytest-dev/pytest-xdist/issues/620 + # * https://stackoverflow.com/a/58614689/595220 + # * https://bugs.python.org/issue35621 + # * https://github.com/python/cpython/pull/14344 + watcher = asyncio.ThreadedChildWatcher() + except AttributeError: # Python < 3.8 + watcher = asyncio.SafeChildWatcher() + watcher.attach_loop(loop) + with contextlib.suppress(NotImplementedError): + policy.set_child_watcher(watcher) + return loop + + +def teardown_test_loop(loop: asyncio.AbstractEventLoop, fast: bool = False) -> None: + """Teardown and cleanup an event_loop created by setup_test_loop.""" + closed = loop.is_closed() + if not closed: + loop.call_soon(loop.stop) + loop.run_forever() + loop.close() + + if not fast: + gc.collect() + + asyncio.set_event_loop(None) + + +def _create_app_mock() -> mock.MagicMock: + def get_dict(app: Any, key: str) -> Any: + return app.__app_dict[key] + + def set_dict(app: Any, key: str, value: Any) -> None: + app.__app_dict[key] = value + + app = mock.MagicMock(spec=Application) + app.__app_dict = {} + app.__getitem__ = get_dict + app.__setitem__ = set_dict + + app._debug = False + app.on_response_prepare = Signal(app) + app.on_response_prepare.freeze() + return app + + +def _create_transport(sslcontext: Optional[SSLContext] = None) -> mock.Mock: + transport = mock.Mock() + + def get_extra_info(key: str) -> Optional[SSLContext]: + if key == "sslcontext": + return sslcontext + else: + return None + + transport.get_extra_info.side_effect = get_extra_info + return transport + + +def make_mocked_request( + method: str, + path: str, + headers: Any = None, + *, + match_info: Any = sentinel, + version: HttpVersion = HttpVersion(1, 1), + closing: bool = False, + app: Any = None, + writer: Any = sentinel, + protocol: Any = sentinel, + transport: Any = sentinel, + payload: Any = sentinel, + sslcontext: Optional[SSLContext] = None, + client_max_size: int = 1024**2, + loop: Any = ..., +) -> Request: + """Creates mocked web.Request testing purposes. + + Useful in unit tests, when spinning full web server is overkill or + specific conditions and errors are hard to trigger. + """ + task = mock.Mock() + if loop is ...: + loop = mock.Mock() + loop.create_future.return_value = () + + if version < HttpVersion(1, 1): + closing = True + + if headers: + headers = CIMultiDictProxy(CIMultiDict(headers)) + raw_hdrs = tuple( + (k.encode("utf-8"), v.encode("utf-8")) for k, v in headers.items() + ) + else: + headers = CIMultiDictProxy(CIMultiDict()) + raw_hdrs = () + + chunked = "chunked" in headers.get(hdrs.TRANSFER_ENCODING, "").lower() + + message = RawRequestMessage( + method, + path, + version, + headers, + raw_hdrs, + closing, + None, + False, + chunked, + URL(path), + ) + if app is None: + app = _create_app_mock() + + if transport is sentinel: + transport = _create_transport(sslcontext) + + if protocol is sentinel: + protocol = mock.Mock() + protocol.transport = transport + + if writer is sentinel: + writer = mock.Mock() + writer.write_headers = make_mocked_coro(None) + writer.write = make_mocked_coro(None) + writer.write_eof = make_mocked_coro(None) + writer.drain = make_mocked_coro(None) + writer.transport = transport + + protocol.transport = transport + protocol.writer = writer + + if payload is sentinel: + payload = mock.Mock() + + req = Request( + message, payload, protocol, writer, task, loop, client_max_size=client_max_size + ) + + match_info = UrlMappingMatchInfo( + {} if match_info is sentinel else match_info, mock.Mock() + ) + match_info.add_app(app) + req._match_info = match_info + + return req + + +def make_mocked_coro( + return_value: Any = sentinel, raise_exception: Any = sentinel +) -> Any: + """Creates a coroutine mock.""" + + async def mock_coro(*args: Any, **kwargs: Any) -> Any: + if raise_exception is not sentinel: + raise raise_exception + if not inspect.isawaitable(return_value): + return return_value + await return_value + + return mock.Mock(wraps=mock_coro) diff --git a/lib/aiohttp/tracing.py b/lib/aiohttp/tracing.py new file mode 100644 index 0000000..d5596a4 --- /dev/null +++ b/lib/aiohttp/tracing.py @@ -0,0 +1,472 @@ +from types import SimpleNamespace +from typing import TYPE_CHECKING, Awaitable, Optional, Type, TypeVar + +import attr +from aiosignal import Signal +from multidict import CIMultiDict +from yarl import URL + +from .client_reqrep import ClientResponse + +if TYPE_CHECKING: # pragma: no cover + from .client import ClientSession + from .typedefs import Protocol + + _ParamT_contra = TypeVar("_ParamT_contra", contravariant=True) + + class _SignalCallback(Protocol[_ParamT_contra]): + def __call__( + self, + __client_session: ClientSession, + __trace_config_ctx: SimpleNamespace, + __params: _ParamT_contra, + ) -> Awaitable[None]: + ... + + +__all__ = ( + "TraceConfig", + "TraceRequestStartParams", + "TraceRequestEndParams", + "TraceRequestExceptionParams", + "TraceConnectionQueuedStartParams", + "TraceConnectionQueuedEndParams", + "TraceConnectionCreateStartParams", + "TraceConnectionCreateEndParams", + "TraceConnectionReuseconnParams", + "TraceDnsResolveHostStartParams", + "TraceDnsResolveHostEndParams", + "TraceDnsCacheHitParams", + "TraceDnsCacheMissParams", + "TraceRequestRedirectParams", + "TraceRequestChunkSentParams", + "TraceResponseChunkReceivedParams", + "TraceRequestHeadersSentParams", +) + + +class TraceConfig: + """First-class used to trace requests launched via ClientSession objects.""" + + def __init__( + self, trace_config_ctx_factory: Type[SimpleNamespace] = SimpleNamespace + ) -> None: + self._on_request_start: Signal[ + _SignalCallback[TraceRequestStartParams] + ] = Signal(self) + self._on_request_chunk_sent: Signal[ + _SignalCallback[TraceRequestChunkSentParams] + ] = Signal(self) + self._on_response_chunk_received: Signal[ + _SignalCallback[TraceResponseChunkReceivedParams] + ] = Signal(self) + self._on_request_end: Signal[_SignalCallback[TraceRequestEndParams]] = Signal( + self + ) + self._on_request_exception: Signal[ + _SignalCallback[TraceRequestExceptionParams] + ] = Signal(self) + self._on_request_redirect: Signal[ + _SignalCallback[TraceRequestRedirectParams] + ] = Signal(self) + self._on_connection_queued_start: Signal[ + _SignalCallback[TraceConnectionQueuedStartParams] + ] = Signal(self) + self._on_connection_queued_end: Signal[ + _SignalCallback[TraceConnectionQueuedEndParams] + ] = Signal(self) + self._on_connection_create_start: Signal[ + _SignalCallback[TraceConnectionCreateStartParams] + ] = Signal(self) + self._on_connection_create_end: Signal[ + _SignalCallback[TraceConnectionCreateEndParams] + ] = Signal(self) + self._on_connection_reuseconn: Signal[ + _SignalCallback[TraceConnectionReuseconnParams] + ] = Signal(self) + self._on_dns_resolvehost_start: Signal[ + _SignalCallback[TraceDnsResolveHostStartParams] + ] = Signal(self) + self._on_dns_resolvehost_end: Signal[ + _SignalCallback[TraceDnsResolveHostEndParams] + ] = Signal(self) + self._on_dns_cache_hit: Signal[ + _SignalCallback[TraceDnsCacheHitParams] + ] = Signal(self) + self._on_dns_cache_miss: Signal[ + _SignalCallback[TraceDnsCacheMissParams] + ] = Signal(self) + self._on_request_headers_sent: Signal[ + _SignalCallback[TraceRequestHeadersSentParams] + ] = Signal(self) + + self._trace_config_ctx_factory = trace_config_ctx_factory + + def trace_config_ctx( + self, trace_request_ctx: Optional[SimpleNamespace] = None + ) -> SimpleNamespace: + """Return a new trace_config_ctx instance""" + return self._trace_config_ctx_factory(trace_request_ctx=trace_request_ctx) + + def freeze(self) -> None: + self._on_request_start.freeze() + self._on_request_chunk_sent.freeze() + self._on_response_chunk_received.freeze() + self._on_request_end.freeze() + self._on_request_exception.freeze() + self._on_request_redirect.freeze() + self._on_connection_queued_start.freeze() + self._on_connection_queued_end.freeze() + self._on_connection_create_start.freeze() + self._on_connection_create_end.freeze() + self._on_connection_reuseconn.freeze() + self._on_dns_resolvehost_start.freeze() + self._on_dns_resolvehost_end.freeze() + self._on_dns_cache_hit.freeze() + self._on_dns_cache_miss.freeze() + self._on_request_headers_sent.freeze() + + @property + def on_request_start(self) -> "Signal[_SignalCallback[TraceRequestStartParams]]": + return self._on_request_start + + @property + def on_request_chunk_sent( + self, + ) -> "Signal[_SignalCallback[TraceRequestChunkSentParams]]": + return self._on_request_chunk_sent + + @property + def on_response_chunk_received( + self, + ) -> "Signal[_SignalCallback[TraceResponseChunkReceivedParams]]": + return self._on_response_chunk_received + + @property + def on_request_end(self) -> "Signal[_SignalCallback[TraceRequestEndParams]]": + return self._on_request_end + + @property + def on_request_exception( + self, + ) -> "Signal[_SignalCallback[TraceRequestExceptionParams]]": + return self._on_request_exception + + @property + def on_request_redirect( + self, + ) -> "Signal[_SignalCallback[TraceRequestRedirectParams]]": + return self._on_request_redirect + + @property + def on_connection_queued_start( + self, + ) -> "Signal[_SignalCallback[TraceConnectionQueuedStartParams]]": + return self._on_connection_queued_start + + @property + def on_connection_queued_end( + self, + ) -> "Signal[_SignalCallback[TraceConnectionQueuedEndParams]]": + return self._on_connection_queued_end + + @property + def on_connection_create_start( + self, + ) -> "Signal[_SignalCallback[TraceConnectionCreateStartParams]]": + return self._on_connection_create_start + + @property + def on_connection_create_end( + self, + ) -> "Signal[_SignalCallback[TraceConnectionCreateEndParams]]": + return self._on_connection_create_end + + @property + def on_connection_reuseconn( + self, + ) -> "Signal[_SignalCallback[TraceConnectionReuseconnParams]]": + return self._on_connection_reuseconn + + @property + def on_dns_resolvehost_start( + self, + ) -> "Signal[_SignalCallback[TraceDnsResolveHostStartParams]]": + return self._on_dns_resolvehost_start + + @property + def on_dns_resolvehost_end( + self, + ) -> "Signal[_SignalCallback[TraceDnsResolveHostEndParams]]": + return self._on_dns_resolvehost_end + + @property + def on_dns_cache_hit(self) -> "Signal[_SignalCallback[TraceDnsCacheHitParams]]": + return self._on_dns_cache_hit + + @property + def on_dns_cache_miss(self) -> "Signal[_SignalCallback[TraceDnsCacheMissParams]]": + return self._on_dns_cache_miss + + @property + def on_request_headers_sent( + self, + ) -> "Signal[_SignalCallback[TraceRequestHeadersSentParams]]": + return self._on_request_headers_sent + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestStartParams: + """Parameters sent by the `on_request_start` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestChunkSentParams: + """Parameters sent by the `on_request_chunk_sent` signal""" + + method: str + url: URL + chunk: bytes + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceResponseChunkReceivedParams: + """Parameters sent by the `on_response_chunk_received` signal""" + + method: str + url: URL + chunk: bytes + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestEndParams: + """Parameters sent by the `on_request_end` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + response: ClientResponse + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestExceptionParams: + """Parameters sent by the `on_request_exception` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + exception: BaseException + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestRedirectParams: + """Parameters sent by the `on_request_redirect` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + response: ClientResponse + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionQueuedStartParams: + """Parameters sent by the `on_connection_queued_start` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionQueuedEndParams: + """Parameters sent by the `on_connection_queued_end` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionCreateStartParams: + """Parameters sent by the `on_connection_create_start` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionCreateEndParams: + """Parameters sent by the `on_connection_create_end` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionReuseconnParams: + """Parameters sent by the `on_connection_reuseconn` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceDnsResolveHostStartParams: + """Parameters sent by the `on_dns_resolvehost_start` signal""" + + host: str + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceDnsResolveHostEndParams: + """Parameters sent by the `on_dns_resolvehost_end` signal""" + + host: str + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceDnsCacheHitParams: + """Parameters sent by the `on_dns_cache_hit` signal""" + + host: str + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceDnsCacheMissParams: + """Parameters sent by the `on_dns_cache_miss` signal""" + + host: str + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestHeadersSentParams: + """Parameters sent by the `on_request_headers_sent` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + + +class Trace: + """Internal dependency holder class. + + Used to keep together the main dependencies used + at the moment of send a signal. + """ + + def __init__( + self, + session: "ClientSession", + trace_config: TraceConfig, + trace_config_ctx: SimpleNamespace, + ) -> None: + self._trace_config = trace_config + self._trace_config_ctx = trace_config_ctx + self._session = session + + async def send_request_start( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + return await self._trace_config.on_request_start.send( + self._session, + self._trace_config_ctx, + TraceRequestStartParams(method, url, headers), + ) + + async def send_request_chunk_sent( + self, method: str, url: URL, chunk: bytes + ) -> None: + return await self._trace_config.on_request_chunk_sent.send( + self._session, + self._trace_config_ctx, + TraceRequestChunkSentParams(method, url, chunk), + ) + + async def send_response_chunk_received( + self, method: str, url: URL, chunk: bytes + ) -> None: + return await self._trace_config.on_response_chunk_received.send( + self._session, + self._trace_config_ctx, + TraceResponseChunkReceivedParams(method, url, chunk), + ) + + async def send_request_end( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + response: ClientResponse, + ) -> None: + return await self._trace_config.on_request_end.send( + self._session, + self._trace_config_ctx, + TraceRequestEndParams(method, url, headers, response), + ) + + async def send_request_exception( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + exception: BaseException, + ) -> None: + return await self._trace_config.on_request_exception.send( + self._session, + self._trace_config_ctx, + TraceRequestExceptionParams(method, url, headers, exception), + ) + + async def send_request_redirect( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + response: ClientResponse, + ) -> None: + return await self._trace_config._on_request_redirect.send( + self._session, + self._trace_config_ctx, + TraceRequestRedirectParams(method, url, headers, response), + ) + + async def send_connection_queued_start(self) -> None: + return await self._trace_config.on_connection_queued_start.send( + self._session, self._trace_config_ctx, TraceConnectionQueuedStartParams() + ) + + async def send_connection_queued_end(self) -> None: + return await self._trace_config.on_connection_queued_end.send( + self._session, self._trace_config_ctx, TraceConnectionQueuedEndParams() + ) + + async def send_connection_create_start(self) -> None: + return await self._trace_config.on_connection_create_start.send( + self._session, self._trace_config_ctx, TraceConnectionCreateStartParams() + ) + + async def send_connection_create_end(self) -> None: + return await self._trace_config.on_connection_create_end.send( + self._session, self._trace_config_ctx, TraceConnectionCreateEndParams() + ) + + async def send_connection_reuseconn(self) -> None: + return await self._trace_config.on_connection_reuseconn.send( + self._session, self._trace_config_ctx, TraceConnectionReuseconnParams() + ) + + async def send_dns_resolvehost_start(self, host: str) -> None: + return await self._trace_config.on_dns_resolvehost_start.send( + self._session, self._trace_config_ctx, TraceDnsResolveHostStartParams(host) + ) + + async def send_dns_resolvehost_end(self, host: str) -> None: + return await self._trace_config.on_dns_resolvehost_end.send( + self._session, self._trace_config_ctx, TraceDnsResolveHostEndParams(host) + ) + + async def send_dns_cache_hit(self, host: str) -> None: + return await self._trace_config.on_dns_cache_hit.send( + self._session, self._trace_config_ctx, TraceDnsCacheHitParams(host) + ) + + async def send_dns_cache_miss(self, host: str) -> None: + return await self._trace_config.on_dns_cache_miss.send( + self._session, self._trace_config_ctx, TraceDnsCacheMissParams(host) + ) + + async def send_request_headers( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + return await self._trace_config._on_request_headers_sent.send( + self._session, + self._trace_config_ctx, + TraceRequestHeadersSentParams(method, url, headers), + ) diff --git a/lib/aiohttp/typedefs.py b/lib/aiohttp/typedefs.py new file mode 100644 index 0000000..84283d9 --- /dev/null +++ b/lib/aiohttp/typedefs.py @@ -0,0 +1,64 @@ +import json +import os +import sys +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Iterable, + Mapping, + Tuple, + Union, +) + +from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy, istr +from yarl import URL + +# These are for other modules to use (to avoid repeating the conditional import). +if sys.version_info >= (3, 8): + from typing import Final as Final, Protocol as Protocol, TypedDict as TypedDict +else: + from typing_extensions import ( # noqa: F401 + Final, + Protocol as Protocol, + TypedDict as TypedDict, + ) + +DEFAULT_JSON_ENCODER = json.dumps +DEFAULT_JSON_DECODER = json.loads + +if TYPE_CHECKING: # pragma: no cover + _CIMultiDict = CIMultiDict[str] + _CIMultiDictProxy = CIMultiDictProxy[str] + _MultiDict = MultiDict[str] + _MultiDictProxy = MultiDictProxy[str] + from http.cookies import BaseCookie, Morsel + + from .web import Request, StreamResponse +else: + _CIMultiDict = CIMultiDict + _CIMultiDictProxy = CIMultiDictProxy + _MultiDict = MultiDict + _MultiDictProxy = MultiDictProxy + +Byteish = Union[bytes, bytearray, memoryview] +JSONEncoder = Callable[[Any], str] +JSONDecoder = Callable[[str], Any] +LooseHeaders = Union[Mapping[Union[str, istr], str], _CIMultiDict, _CIMultiDictProxy] +RawHeaders = Tuple[Tuple[bytes, bytes], ...] +StrOrURL = Union[str, URL] + +LooseCookiesMappings = Mapping[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]] +LooseCookiesIterables = Iterable[ + Tuple[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]] +] +LooseCookies = Union[ + LooseCookiesMappings, + LooseCookiesIterables, + "BaseCookie[str]", +] + +Handler = Callable[["Request"], Awaitable["StreamResponse"]] + +PathLike = Union[str, "os.PathLike[str]"] diff --git a/lib/aiohttp/web.py b/lib/aiohttp/web.py new file mode 100644 index 0000000..cefae2b --- /dev/null +++ b/lib/aiohttp/web.py @@ -0,0 +1,588 @@ +import asyncio +import logging +import socket +import sys +from argparse import ArgumentParser +from collections.abc import Iterable +from importlib import import_module +from typing import ( + Any, + Awaitable, + Callable, + Iterable as TypingIterable, + List, + Optional, + Set, + Type, + Union, + cast, +) + +from .abc import AbstractAccessLogger +from .helpers import all_tasks +from .log import access_logger +from .web_app import Application as Application, CleanupError as CleanupError +from .web_exceptions import ( + HTTPAccepted as HTTPAccepted, + HTTPBadGateway as HTTPBadGateway, + HTTPBadRequest as HTTPBadRequest, + HTTPClientError as HTTPClientError, + HTTPConflict as HTTPConflict, + HTTPCreated as HTTPCreated, + HTTPError as HTTPError, + HTTPException as HTTPException, + HTTPExpectationFailed as HTTPExpectationFailed, + HTTPFailedDependency as HTTPFailedDependency, + HTTPForbidden as HTTPForbidden, + HTTPFound as HTTPFound, + HTTPGatewayTimeout as HTTPGatewayTimeout, + HTTPGone as HTTPGone, + HTTPInsufficientStorage as HTTPInsufficientStorage, + HTTPInternalServerError as HTTPInternalServerError, + HTTPLengthRequired as HTTPLengthRequired, + HTTPMethodNotAllowed as HTTPMethodNotAllowed, + HTTPMisdirectedRequest as HTTPMisdirectedRequest, + HTTPMovedPermanently as HTTPMovedPermanently, + HTTPMultipleChoices as HTTPMultipleChoices, + HTTPNetworkAuthenticationRequired as HTTPNetworkAuthenticationRequired, + HTTPNoContent as HTTPNoContent, + HTTPNonAuthoritativeInformation as HTTPNonAuthoritativeInformation, + HTTPNotAcceptable as HTTPNotAcceptable, + HTTPNotExtended as HTTPNotExtended, + HTTPNotFound as HTTPNotFound, + HTTPNotImplemented as HTTPNotImplemented, + HTTPNotModified as HTTPNotModified, + HTTPOk as HTTPOk, + HTTPPartialContent as HTTPPartialContent, + HTTPPaymentRequired as HTTPPaymentRequired, + HTTPPermanentRedirect as HTTPPermanentRedirect, + HTTPPreconditionFailed as HTTPPreconditionFailed, + HTTPPreconditionRequired as HTTPPreconditionRequired, + HTTPProxyAuthenticationRequired as HTTPProxyAuthenticationRequired, + HTTPRedirection as HTTPRedirection, + HTTPRequestEntityTooLarge as HTTPRequestEntityTooLarge, + HTTPRequestHeaderFieldsTooLarge as HTTPRequestHeaderFieldsTooLarge, + HTTPRequestRangeNotSatisfiable as HTTPRequestRangeNotSatisfiable, + HTTPRequestTimeout as HTTPRequestTimeout, + HTTPRequestURITooLong as HTTPRequestURITooLong, + HTTPResetContent as HTTPResetContent, + HTTPSeeOther as HTTPSeeOther, + HTTPServerError as HTTPServerError, + HTTPServiceUnavailable as HTTPServiceUnavailable, + HTTPSuccessful as HTTPSuccessful, + HTTPTemporaryRedirect as HTTPTemporaryRedirect, + HTTPTooManyRequests as HTTPTooManyRequests, + HTTPUnauthorized as HTTPUnauthorized, + HTTPUnavailableForLegalReasons as HTTPUnavailableForLegalReasons, + HTTPUnprocessableEntity as HTTPUnprocessableEntity, + HTTPUnsupportedMediaType as HTTPUnsupportedMediaType, + HTTPUpgradeRequired as HTTPUpgradeRequired, + HTTPUseProxy as HTTPUseProxy, + HTTPVariantAlsoNegotiates as HTTPVariantAlsoNegotiates, + HTTPVersionNotSupported as HTTPVersionNotSupported, +) +from .web_fileresponse import FileResponse as FileResponse +from .web_log import AccessLogger +from .web_middlewares import ( + middleware as middleware, + normalize_path_middleware as normalize_path_middleware, +) +from .web_protocol import ( + PayloadAccessError as PayloadAccessError, + RequestHandler as RequestHandler, + RequestPayloadError as RequestPayloadError, +) +from .web_request import ( + BaseRequest as BaseRequest, + FileField as FileField, + Request as Request, +) +from .web_response import ( + ContentCoding as ContentCoding, + Response as Response, + StreamResponse as StreamResponse, + json_response as json_response, +) +from .web_routedef import ( + AbstractRouteDef as AbstractRouteDef, + RouteDef as RouteDef, + RouteTableDef as RouteTableDef, + StaticDef as StaticDef, + delete as delete, + get as get, + head as head, + options as options, + patch as patch, + post as post, + put as put, + route as route, + static as static, + view as view, +) +from .web_runner import ( + AppRunner as AppRunner, + BaseRunner as BaseRunner, + BaseSite as BaseSite, + GracefulExit as GracefulExit, + NamedPipeSite as NamedPipeSite, + ServerRunner as ServerRunner, + SockSite as SockSite, + TCPSite as TCPSite, + UnixSite as UnixSite, +) +from .web_server import Server as Server +from .web_urldispatcher import ( + AbstractResource as AbstractResource, + AbstractRoute as AbstractRoute, + DynamicResource as DynamicResource, + PlainResource as PlainResource, + PrefixedSubAppResource as PrefixedSubAppResource, + Resource as Resource, + ResourceRoute as ResourceRoute, + StaticResource as StaticResource, + UrlDispatcher as UrlDispatcher, + UrlMappingMatchInfo as UrlMappingMatchInfo, + View as View, +) +from .web_ws import ( + WebSocketReady as WebSocketReady, + WebSocketResponse as WebSocketResponse, + WSMsgType as WSMsgType, +) + +__all__ = ( + # web_app + "Application", + "CleanupError", + # web_exceptions + "HTTPAccepted", + "HTTPBadGateway", + "HTTPBadRequest", + "HTTPClientError", + "HTTPConflict", + "HTTPCreated", + "HTTPError", + "HTTPException", + "HTTPExpectationFailed", + "HTTPFailedDependency", + "HTTPForbidden", + "HTTPFound", + "HTTPGatewayTimeout", + "HTTPGone", + "HTTPInsufficientStorage", + "HTTPInternalServerError", + "HTTPLengthRequired", + "HTTPMethodNotAllowed", + "HTTPMisdirectedRequest", + "HTTPMovedPermanently", + "HTTPMultipleChoices", + "HTTPNetworkAuthenticationRequired", + "HTTPNoContent", + "HTTPNonAuthoritativeInformation", + "HTTPNotAcceptable", + "HTTPNotExtended", + "HTTPNotFound", + "HTTPNotImplemented", + "HTTPNotModified", + "HTTPOk", + "HTTPPartialContent", + "HTTPPaymentRequired", + "HTTPPermanentRedirect", + "HTTPPreconditionFailed", + "HTTPPreconditionRequired", + "HTTPProxyAuthenticationRequired", + "HTTPRedirection", + "HTTPRequestEntityTooLarge", + "HTTPRequestHeaderFieldsTooLarge", + "HTTPRequestRangeNotSatisfiable", + "HTTPRequestTimeout", + "HTTPRequestURITooLong", + "HTTPResetContent", + "HTTPSeeOther", + "HTTPServerError", + "HTTPServiceUnavailable", + "HTTPSuccessful", + "HTTPTemporaryRedirect", + "HTTPTooManyRequests", + "HTTPUnauthorized", + "HTTPUnavailableForLegalReasons", + "HTTPUnprocessableEntity", + "HTTPUnsupportedMediaType", + "HTTPUpgradeRequired", + "HTTPUseProxy", + "HTTPVariantAlsoNegotiates", + "HTTPVersionNotSupported", + # web_fileresponse + "FileResponse", + # web_middlewares + "middleware", + "normalize_path_middleware", + # web_protocol + "PayloadAccessError", + "RequestHandler", + "RequestPayloadError", + # web_request + "BaseRequest", + "FileField", + "Request", + # web_response + "ContentCoding", + "Response", + "StreamResponse", + "json_response", + # web_routedef + "AbstractRouteDef", + "RouteDef", + "RouteTableDef", + "StaticDef", + "delete", + "get", + "head", + "options", + "patch", + "post", + "put", + "route", + "static", + "view", + # web_runner + "AppRunner", + "BaseRunner", + "BaseSite", + "GracefulExit", + "ServerRunner", + "SockSite", + "TCPSite", + "UnixSite", + "NamedPipeSite", + # web_server + "Server", + # web_urldispatcher + "AbstractResource", + "AbstractRoute", + "DynamicResource", + "PlainResource", + "PrefixedSubAppResource", + "Resource", + "ResourceRoute", + "StaticResource", + "UrlDispatcher", + "UrlMappingMatchInfo", + "View", + # web_ws + "WebSocketReady", + "WebSocketResponse", + "WSMsgType", + # web + "run_app", +) + + +try: + from ssl import SSLContext +except ImportError: # pragma: no cover + SSLContext = Any # type: ignore[misc,assignment] + +HostSequence = TypingIterable[str] + + +async def _run_app( + app: Union[Application, Awaitable[Application]], + *, + host: Optional[Union[str, HostSequence]] = None, + port: Optional[int] = None, + path: Optional[str] = None, + sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, + shutdown_timeout: float = 60.0, + keepalive_timeout: float = 75.0, + ssl_context: Optional[SSLContext] = None, + print: Callable[..., None] = print, + backlog: int = 128, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log_format: str = AccessLogger.LOG_FORMAT, + access_log: Optional[logging.Logger] = access_logger, + handle_signals: bool = True, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, +) -> None: + # A internal functio to actually do all dirty job for application running + if asyncio.iscoroutine(app): + app = await app # type: ignore[misc] + + app = cast(Application, app) + + runner = AppRunner( + app, + handle_signals=handle_signals, + access_log_class=access_log_class, + access_log_format=access_log_format, + access_log=access_log, + keepalive_timeout=keepalive_timeout, + ) + + await runner.setup() + + sites: List[BaseSite] = [] + + try: + if host is not None: + if isinstance(host, (str, bytes, bytearray, memoryview)): + sites.append( + TCPSite( + runner, + host, + port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + else: + for h in host: + sites.append( + TCPSite( + runner, + h, + port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + elif path is None and sock is None or port is not None: + sites.append( + TCPSite( + runner, + port=port, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + + if path is not None: + if isinstance(path, (str, bytes, bytearray, memoryview)): + sites.append( + UnixSite( + runner, + path, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + else: + for p in path: + sites.append( + UnixSite( + runner, + p, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + + if sock is not None: + if not isinstance(sock, Iterable): + sites.append( + SockSite( + runner, + sock, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + else: + for s in sock: + sites.append( + SockSite( + runner, + s, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + for site in sites: + await site.start() + + if print: # pragma: no branch + names = sorted(str(s.name) for s in runner.sites) + print( + "======== Running on {} ========\n" + "(Press CTRL+C to quit)".format(", ".join(names)) + ) + + # sleep forever by 1 hour intervals, + # on Windows before Python 3.8 wake up every 1 second to handle + # Ctrl+C smoothly + if sys.platform == "win32" and sys.version_info < (3, 8): + delay = 1 + else: + delay = 3600 + + while True: + await asyncio.sleep(delay) + finally: + await runner.cleanup() + + +def _cancel_tasks( + to_cancel: Set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop +) -> None: + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) + + +def run_app( + app: Union[Application, Awaitable[Application]], + *, + host: Optional[Union[str, HostSequence]] = None, + port: Optional[int] = None, + path: Optional[str] = None, + sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, + shutdown_timeout: float = 60.0, + keepalive_timeout: float = 75.0, + ssl_context: Optional[SSLContext] = None, + print: Callable[..., None] = print, + backlog: int = 128, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log_format: str = AccessLogger.LOG_FORMAT, + access_log: Optional[logging.Logger] = access_logger, + handle_signals: bool = True, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> None: + """Run an app locally""" + if loop is None: + loop = asyncio.new_event_loop() + + # Configure if and only if in debugging mode and using the default logger + if loop.get_debug() and access_log and access_log.name == "aiohttp.access": + if access_log.level == logging.NOTSET: + access_log.setLevel(logging.DEBUG) + if not access_log.hasHandlers(): + access_log.addHandler(logging.StreamHandler()) + + main_task = loop.create_task( + _run_app( + app, + host=host, + port=port, + path=path, + sock=sock, + shutdown_timeout=shutdown_timeout, + keepalive_timeout=keepalive_timeout, + ssl_context=ssl_context, + print=print, + backlog=backlog, + access_log_class=access_log_class, + access_log_format=access_log_format, + access_log=access_log, + handle_signals=handle_signals, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + + try: + asyncio.set_event_loop(loop) + loop.run_until_complete(main_task) + except (GracefulExit, KeyboardInterrupt): # pragma: no cover + pass + finally: + _cancel_tasks({main_task}, loop) + _cancel_tasks(all_tasks(loop), loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + loop.close() + + +def main(argv: List[str]) -> None: + arg_parser = ArgumentParser( + description="aiohttp.web Application server", prog="aiohttp.web" + ) + arg_parser.add_argument( + "entry_func", + help=( + "Callable returning the `aiohttp.web.Application` instance to " + "run. Should be specified in the 'module:function' syntax." + ), + metavar="entry-func", + ) + arg_parser.add_argument( + "-H", + "--hostname", + help="TCP/IP hostname to serve on (default: %(default)r)", + default="localhost", + ) + arg_parser.add_argument( + "-P", + "--port", + help="TCP/IP port to serve on (default: %(default)r)", + type=int, + default="8080", + ) + arg_parser.add_argument( + "-U", + "--path", + help="Unix file system path to serve on. Specifying a path will cause " + "hostname and port arguments to be ignored.", + ) + args, extra_argv = arg_parser.parse_known_args(argv) + + # Import logic + mod_str, _, func_str = args.entry_func.partition(":") + if not func_str or not mod_str: + arg_parser.error("'entry-func' not in 'module:function' syntax") + if mod_str.startswith("."): + arg_parser.error("relative module names not supported") + try: + module = import_module(mod_str) + except ImportError as ex: + arg_parser.error(f"unable to import {mod_str}: {ex}") + try: + func = getattr(module, func_str) + except AttributeError: + arg_parser.error(f"module {mod_str!r} has no attribute {func_str!r}") + + # Compatibility logic + if args.path is not None and not hasattr(socket, "AF_UNIX"): + arg_parser.error( + "file system paths not supported by your operating" " environment" + ) + + logging.basicConfig(level=logging.DEBUG) + + app = func(extra_argv) + run_app(app, host=args.hostname, port=args.port, path=args.path) + arg_parser.exit(message="Stopped\n") + + +if __name__ == "__main__": # pragma: no branch + main(sys.argv[1:]) # pragma: no cover diff --git a/lib/aiohttp/web_app.py b/lib/aiohttp/web_app.py new file mode 100644 index 0000000..8fd4471 --- /dev/null +++ b/lib/aiohttp/web_app.py @@ -0,0 +1,557 @@ +import asyncio +import logging +import warnings +from functools import partial, update_wrapper +from typing import ( + TYPE_CHECKING, + Any, + AsyncIterator, + Awaitable, + Callable, + Dict, + Iterable, + Iterator, + List, + Mapping, + MutableMapping, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) + +from aiosignal import Signal +from frozenlist import FrozenList + +from . import hdrs +from .abc import ( + AbstractAccessLogger, + AbstractMatchInfo, + AbstractRouter, + AbstractStreamWriter, +) +from .helpers import DEBUG +from .http_parser import RawRequestMessage +from .log import web_logger +from .streams import StreamReader +from .web_log import AccessLogger +from .web_middlewares import _fix_request_current_app +from .web_protocol import RequestHandler +from .web_request import Request +from .web_response import StreamResponse +from .web_routedef import AbstractRouteDef +from .web_server import Server +from .web_urldispatcher import ( + AbstractResource, + AbstractRoute, + Domain, + MaskDomain, + MatchedSubAppResource, + PrefixedSubAppResource, + UrlDispatcher, +) + +__all__ = ("Application", "CleanupError") + + +if TYPE_CHECKING: # pragma: no cover + from .typedefs import Handler + + _AppSignal = Signal[Callable[["Application"], Awaitable[None]]] + _RespPrepareSignal = Signal[Callable[[Request, StreamResponse], Awaitable[None]]] + _Middleware = Union[ + Callable[[Request, Handler], Awaitable[StreamResponse]], + Callable[["Application", Handler], Awaitable[Handler]], # old-style + ] + _Middlewares = FrozenList[_Middleware] + _MiddlewaresHandlers = Optional[Sequence[Tuple[_Middleware, bool]]] + _Subapps = List["Application"] +else: + # No type checker mode, skip types + _AppSignal = Signal + _RespPrepareSignal = Signal + _Middleware = Callable + _Middlewares = FrozenList + _MiddlewaresHandlers = Optional[Sequence] + _Subapps = List + + +class Application(MutableMapping[str, Any]): + ATTRS = frozenset( + [ + "logger", + "_debug", + "_router", + "_loop", + "_handler_args", + "_middlewares", + "_middlewares_handlers", + "_run_middlewares", + "_state", + "_frozen", + "_pre_frozen", + "_subapps", + "_on_response_prepare", + "_on_startup", + "_on_shutdown", + "_on_cleanup", + "_client_max_size", + "_cleanup_ctx", + ] + ) + + def __init__( + self, + *, + logger: logging.Logger = web_logger, + router: Optional[UrlDispatcher] = None, + middlewares: Iterable[_Middleware] = (), + handler_args: Optional[Mapping[str, Any]] = None, + client_max_size: int = 1024**2, + loop: Optional[asyncio.AbstractEventLoop] = None, + debug: Any = ..., # mypy doesn't support ellipsis + ) -> None: + if router is None: + router = UrlDispatcher() + else: + warnings.warn( + "router argument is deprecated", DeprecationWarning, stacklevel=2 + ) + assert isinstance(router, AbstractRouter), router + + if loop is not None: + warnings.warn( + "loop argument is deprecated", DeprecationWarning, stacklevel=2 + ) + + if debug is not ...: + warnings.warn( + "debug argument is deprecated", DeprecationWarning, stacklevel=2 + ) + self._debug = debug + self._router: UrlDispatcher = router + self._loop = loop + self._handler_args = handler_args + self.logger = logger + + self._middlewares: _Middlewares = FrozenList(middlewares) + + # initialized on freezing + self._middlewares_handlers: _MiddlewaresHandlers = None + # initialized on freezing + self._run_middlewares: Optional[bool] = None + + self._state: Dict[str, Any] = {} + self._frozen = False + self._pre_frozen = False + self._subapps: _Subapps = [] + + self._on_response_prepare: _RespPrepareSignal = Signal(self) + self._on_startup: _AppSignal = Signal(self) + self._on_shutdown: _AppSignal = Signal(self) + self._on_cleanup: _AppSignal = Signal(self) + self._cleanup_ctx = CleanupContext() + self._on_startup.append(self._cleanup_ctx._on_startup) + self._on_cleanup.append(self._cleanup_ctx._on_cleanup) + self._client_max_size = client_max_size + + def __init_subclass__(cls: Type["Application"]) -> None: + warnings.warn( + "Inheritance class {} from web.Application " + "is discouraged".format(cls.__name__), + DeprecationWarning, + stacklevel=2, + ) + + if DEBUG: # pragma: no cover + + def __setattr__(self, name: str, val: Any) -> None: + if name not in self.ATTRS: + warnings.warn( + "Setting custom web.Application.{} attribute " + "is discouraged".format(name), + DeprecationWarning, + stacklevel=2, + ) + super().__setattr__(name, val) + + # MutableMapping API + + def __eq__(self, other: object) -> bool: + return self is other + + def __getitem__(self, key: str) -> Any: + return self._state[key] + + def _check_frozen(self) -> None: + if self._frozen: + warnings.warn( + "Changing state of started or joined " "application is deprecated", + DeprecationWarning, + stacklevel=3, + ) + + def __setitem__(self, key: str, value: Any) -> None: + self._check_frozen() + self._state[key] = value + + def __delitem__(self, key: str) -> None: + self._check_frozen() + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[str]: + return iter(self._state) + + ######## + @property + def loop(self) -> asyncio.AbstractEventLoop: + # Technically the loop can be None + # but we mask it by explicit type cast + # to provide more convinient type annotation + warnings.warn("loop property is deprecated", DeprecationWarning, stacklevel=2) + return cast(asyncio.AbstractEventLoop, self._loop) + + def _set_loop(self, loop: Optional[asyncio.AbstractEventLoop]) -> None: + if loop is None: + loop = asyncio.get_event_loop() + if self._loop is not None and self._loop is not loop: + raise RuntimeError( + "web.Application instance initialized with different loop" + ) + + self._loop = loop + + # set loop debug + if self._debug is ...: + self._debug = loop.get_debug() + + # set loop to sub applications + for subapp in self._subapps: + subapp._set_loop(loop) + + @property + def pre_frozen(self) -> bool: + return self._pre_frozen + + def pre_freeze(self) -> None: + if self._pre_frozen: + return + + self._pre_frozen = True + self._middlewares.freeze() + self._router.freeze() + self._on_response_prepare.freeze() + self._cleanup_ctx.freeze() + self._on_startup.freeze() + self._on_shutdown.freeze() + self._on_cleanup.freeze() + self._middlewares_handlers = tuple(self._prepare_middleware()) + + # If current app and any subapp do not have middlewares avoid run all + # of the code footprint that it implies, which have a middleware + # hardcoded per app that sets up the current_app attribute. If no + # middlewares are configured the handler will receive the proper + # current_app without needing all of this code. + self._run_middlewares = True if self.middlewares else False + + for subapp in self._subapps: + subapp.pre_freeze() + self._run_middlewares = self._run_middlewares or subapp._run_middlewares + + @property + def frozen(self) -> bool: + return self._frozen + + def freeze(self) -> None: + if self._frozen: + return + + self.pre_freeze() + self._frozen = True + for subapp in self._subapps: + subapp.freeze() + + @property + def debug(self) -> bool: + warnings.warn("debug property is deprecated", DeprecationWarning, stacklevel=2) + return self._debug # type: ignore[no-any-return] + + def _reg_subapp_signals(self, subapp: "Application") -> None: + def reg_handler(signame: str) -> None: + subsig = getattr(subapp, signame) + + async def handler(app: "Application") -> None: + await subsig.send(subapp) + + appsig = getattr(self, signame) + appsig.append(handler) + + reg_handler("on_startup") + reg_handler("on_shutdown") + reg_handler("on_cleanup") + + def add_subapp(self, prefix: str, subapp: "Application") -> AbstractResource: + if not isinstance(prefix, str): + raise TypeError("Prefix must be str") + prefix = prefix.rstrip("/") + if not prefix: + raise ValueError("Prefix cannot be empty") + factory = partial(PrefixedSubAppResource, prefix, subapp) + return self._add_subapp(factory, subapp) + + def _add_subapp( + self, resource_factory: Callable[[], AbstractResource], subapp: "Application" + ) -> AbstractResource: + if self.frozen: + raise RuntimeError("Cannot add sub application to frozen application") + if subapp.frozen: + raise RuntimeError("Cannot add frozen application") + resource = resource_factory() + self.router.register_resource(resource) + self._reg_subapp_signals(subapp) + self._subapps.append(subapp) + subapp.pre_freeze() + if self._loop is not None: + subapp._set_loop(self._loop) + return resource + + def add_domain(self, domain: str, subapp: "Application") -> AbstractResource: + if not isinstance(domain, str): + raise TypeError("Domain must be str") + elif "*" in domain: + rule: Domain = MaskDomain(domain) + else: + rule = Domain(domain) + factory = partial(MatchedSubAppResource, rule, subapp) + return self._add_subapp(factory, subapp) + + def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]: + return self.router.add_routes(routes) + + @property + def on_response_prepare(self) -> _RespPrepareSignal: + return self._on_response_prepare + + @property + def on_startup(self) -> _AppSignal: + return self._on_startup + + @property + def on_shutdown(self) -> _AppSignal: + return self._on_shutdown + + @property + def on_cleanup(self) -> _AppSignal: + return self._on_cleanup + + @property + def cleanup_ctx(self) -> "CleanupContext": + return self._cleanup_ctx + + @property + def router(self) -> UrlDispatcher: + return self._router + + @property + def middlewares(self) -> _Middlewares: + return self._middlewares + + def _make_handler( + self, + *, + loop: Optional[asyncio.AbstractEventLoop] = None, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + **kwargs: Any, + ) -> Server: + + if not issubclass(access_log_class, AbstractAccessLogger): + raise TypeError( + "access_log_class must be subclass of " + "aiohttp.abc.AbstractAccessLogger, got {}".format(access_log_class) + ) + + self._set_loop(loop) + self.freeze() + + kwargs["debug"] = self._debug + kwargs["access_log_class"] = access_log_class + if self._handler_args: + for k, v in self._handler_args.items(): + kwargs[k] = v + + return Server( + self._handle, # type: ignore[arg-type] + request_factory=self._make_request, + loop=self._loop, + **kwargs, + ) + + def make_handler( + self, + *, + loop: Optional[asyncio.AbstractEventLoop] = None, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + **kwargs: Any, + ) -> Server: + + warnings.warn( + "Application.make_handler(...) is deprecated, " "use AppRunner API instead", + DeprecationWarning, + stacklevel=2, + ) + + return self._make_handler( + loop=loop, access_log_class=access_log_class, **kwargs + ) + + async def startup(self) -> None: + """Causes on_startup signal + + Should be called in the event loop along with the request handler. + """ + await self.on_startup.send(self) + + async def shutdown(self) -> None: + """Causes on_shutdown signal + + Should be called before cleanup() + """ + await self.on_shutdown.send(self) + + async def cleanup(self) -> None: + """Causes on_cleanup signal + + Should be called after shutdown() + """ + if self.on_cleanup.frozen: + await self.on_cleanup.send(self) + else: + # If an exception occurs in startup, ensure cleanup contexts are completed. + await self._cleanup_ctx._on_cleanup(self) + + def _make_request( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: RequestHandler, + writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + _cls: Type[Request] = Request, + ) -> Request: + return _cls( + message, + payload, + protocol, + writer, + task, + self._loop, + client_max_size=self._client_max_size, + ) + + def _prepare_middleware(self) -> Iterator[Tuple[_Middleware, bool]]: + for m in reversed(self._middlewares): + if getattr(m, "__middleware_version__", None) == 1: + yield m, True + else: + warnings.warn( + 'old-style middleware "{!r}" deprecated, ' "see #2252".format(m), + DeprecationWarning, + stacklevel=2, + ) + yield m, False + + yield _fix_request_current_app(self), True + + async def _handle(self, request: Request) -> StreamResponse: + loop = asyncio.get_event_loop() + debug = loop.get_debug() + match_info = await self._router.resolve(request) + if debug: # pragma: no cover + if not isinstance(match_info, AbstractMatchInfo): + raise TypeError( + "match_info should be AbstractMatchInfo " + "instance, not {!r}".format(match_info) + ) + match_info.add_app(self) + + match_info.freeze() + + resp = None + request._match_info = match_info + expect = request.headers.get(hdrs.EXPECT) + if expect: + resp = await match_info.expect_handler(request) + await request.writer.drain() + + if resp is None: + handler = match_info.handler + + if self._run_middlewares: + for app in match_info.apps[::-1]: + for m, new_style in app._middlewares_handlers: # type: ignore[union-attr] # noqa + if new_style: + handler = update_wrapper( + partial(m, handler=handler), handler + ) + else: + handler = await m(app, handler) # type: ignore[arg-type] + + resp = await handler(request) + + return resp + + def __call__(self) -> "Application": + """gunicorn compatibility""" + return self + + def __repr__(self) -> str: + return f"" + + def __bool__(self) -> bool: + return True + + +class CleanupError(RuntimeError): + @property + def exceptions(self) -> List[BaseException]: + return cast(List[BaseException], self.args[1]) + + +if TYPE_CHECKING: # pragma: no cover + _CleanupContextBase = FrozenList[Callable[[Application], AsyncIterator[None]]] +else: + _CleanupContextBase = FrozenList + + +class CleanupContext(_CleanupContextBase): + def __init__(self) -> None: + super().__init__() + self._exits: List[AsyncIterator[None]] = [] + + async def _on_startup(self, app: Application) -> None: + for cb in self: + it = cb(app).__aiter__() + await it.__anext__() + self._exits.append(it) + + async def _on_cleanup(self, app: Application) -> None: + errors = [] + for it in reversed(self._exits): + try: + await it.__anext__() + except StopAsyncIteration: + pass + except Exception as exc: + errors.append(exc) + else: + errors.append(RuntimeError(f"{it!r} has more than one 'yield'")) + if errors: + if len(errors) == 1: + raise errors[0] + else: + raise CleanupError("Multiple errors on cleanup stage", errors) diff --git a/lib/aiohttp/web_exceptions.py b/lib/aiohttp/web_exceptions.py new file mode 100644 index 0000000..ae706a1 --- /dev/null +++ b/lib/aiohttp/web_exceptions.py @@ -0,0 +1,441 @@ +import warnings +from typing import Any, Dict, Iterable, List, Optional, Set # noqa + +from yarl import URL + +from .typedefs import LooseHeaders, StrOrURL +from .web_response import Response + +__all__ = ( + "HTTPException", + "HTTPError", + "HTTPRedirection", + "HTTPSuccessful", + "HTTPOk", + "HTTPCreated", + "HTTPAccepted", + "HTTPNonAuthoritativeInformation", + "HTTPNoContent", + "HTTPResetContent", + "HTTPPartialContent", + "HTTPMultipleChoices", + "HTTPMovedPermanently", + "HTTPFound", + "HTTPSeeOther", + "HTTPNotModified", + "HTTPUseProxy", + "HTTPTemporaryRedirect", + "HTTPPermanentRedirect", + "HTTPClientError", + "HTTPBadRequest", + "HTTPUnauthorized", + "HTTPPaymentRequired", + "HTTPForbidden", + "HTTPNotFound", + "HTTPMethodNotAllowed", + "HTTPNotAcceptable", + "HTTPProxyAuthenticationRequired", + "HTTPRequestTimeout", + "HTTPConflict", + "HTTPGone", + "HTTPLengthRequired", + "HTTPPreconditionFailed", + "HTTPRequestEntityTooLarge", + "HTTPRequestURITooLong", + "HTTPUnsupportedMediaType", + "HTTPRequestRangeNotSatisfiable", + "HTTPExpectationFailed", + "HTTPMisdirectedRequest", + "HTTPUnprocessableEntity", + "HTTPFailedDependency", + "HTTPUpgradeRequired", + "HTTPPreconditionRequired", + "HTTPTooManyRequests", + "HTTPRequestHeaderFieldsTooLarge", + "HTTPUnavailableForLegalReasons", + "HTTPServerError", + "HTTPInternalServerError", + "HTTPNotImplemented", + "HTTPBadGateway", + "HTTPServiceUnavailable", + "HTTPGatewayTimeout", + "HTTPVersionNotSupported", + "HTTPVariantAlsoNegotiates", + "HTTPInsufficientStorage", + "HTTPNotExtended", + "HTTPNetworkAuthenticationRequired", +) + + +############################################################ +# HTTP Exceptions +############################################################ + + +class HTTPException(Response, Exception): + + # You should set in subclasses: + # status = 200 + + status_code = -1 + empty_body = False + + __http_exception__ = True + + def __init__( + self, + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + body: Any = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + if body is not None: + warnings.warn( + "body argument is deprecated for http web exceptions", + DeprecationWarning, + ) + Response.__init__( + self, + status=self.status_code, + headers=headers, + reason=reason, + body=body, + text=text, + content_type=content_type, + ) + Exception.__init__(self, self.reason) + if self.body is None and not self.empty_body: + self.text = f"{self.status}: {self.reason}" + + def __bool__(self) -> bool: + return True + + +class HTTPError(HTTPException): + """Base class for exceptions with status codes in the 400s and 500s.""" + + +class HTTPRedirection(HTTPException): + """Base class for exceptions with status codes in the 300s.""" + + +class HTTPSuccessful(HTTPException): + """Base class for exceptions with status codes in the 200s.""" + + +class HTTPOk(HTTPSuccessful): + status_code = 200 + + +class HTTPCreated(HTTPSuccessful): + status_code = 201 + + +class HTTPAccepted(HTTPSuccessful): + status_code = 202 + + +class HTTPNonAuthoritativeInformation(HTTPSuccessful): + status_code = 203 + + +class HTTPNoContent(HTTPSuccessful): + status_code = 204 + empty_body = True + + +class HTTPResetContent(HTTPSuccessful): + status_code = 205 + empty_body = True + + +class HTTPPartialContent(HTTPSuccessful): + status_code = 206 + + +############################################################ +# 3xx redirection +############################################################ + + +class _HTTPMove(HTTPRedirection): + def __init__( + self, + location: StrOrURL, + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + body: Any = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + if not location: + raise ValueError("HTTP redirects need a location to redirect to.") + super().__init__( + headers=headers, + reason=reason, + body=body, + text=text, + content_type=content_type, + ) + self.headers["Location"] = str(URL(location)) + self.location = location + + +class HTTPMultipleChoices(_HTTPMove): + status_code = 300 + + +class HTTPMovedPermanently(_HTTPMove): + status_code = 301 + + +class HTTPFound(_HTTPMove): + status_code = 302 + + +# This one is safe after a POST (the redirected location will be +# retrieved with GET): +class HTTPSeeOther(_HTTPMove): + status_code = 303 + + +class HTTPNotModified(HTTPRedirection): + # FIXME: this should include a date or etag header + status_code = 304 + empty_body = True + + +class HTTPUseProxy(_HTTPMove): + # Not a move, but looks a little like one + status_code = 305 + + +class HTTPTemporaryRedirect(_HTTPMove): + status_code = 307 + + +class HTTPPermanentRedirect(_HTTPMove): + status_code = 308 + + +############################################################ +# 4xx client error +############################################################ + + +class HTTPClientError(HTTPError): + pass + + +class HTTPBadRequest(HTTPClientError): + status_code = 400 + + +class HTTPUnauthorized(HTTPClientError): + status_code = 401 + + +class HTTPPaymentRequired(HTTPClientError): + status_code = 402 + + +class HTTPForbidden(HTTPClientError): + status_code = 403 + + +class HTTPNotFound(HTTPClientError): + status_code = 404 + + +class HTTPMethodNotAllowed(HTTPClientError): + status_code = 405 + + def __init__( + self, + method: str, + allowed_methods: Iterable[str], + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + body: Any = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + allow = ",".join(sorted(allowed_methods)) + super().__init__( + headers=headers, + reason=reason, + body=body, + text=text, + content_type=content_type, + ) + self.headers["Allow"] = allow + self.allowed_methods: Set[str] = set(allowed_methods) + self.method = method.upper() + + +class HTTPNotAcceptable(HTTPClientError): + status_code = 406 + + +class HTTPProxyAuthenticationRequired(HTTPClientError): + status_code = 407 + + +class HTTPRequestTimeout(HTTPClientError): + status_code = 408 + + +class HTTPConflict(HTTPClientError): + status_code = 409 + + +class HTTPGone(HTTPClientError): + status_code = 410 + + +class HTTPLengthRequired(HTTPClientError): + status_code = 411 + + +class HTTPPreconditionFailed(HTTPClientError): + status_code = 412 + + +class HTTPRequestEntityTooLarge(HTTPClientError): + status_code = 413 + + def __init__(self, max_size: float, actual_size: float, **kwargs: Any) -> None: + kwargs.setdefault( + "text", + "Maximum request body size {} exceeded, " + "actual body size {}".format(max_size, actual_size), + ) + super().__init__(**kwargs) + + +class HTTPRequestURITooLong(HTTPClientError): + status_code = 414 + + +class HTTPUnsupportedMediaType(HTTPClientError): + status_code = 415 + + +class HTTPRequestRangeNotSatisfiable(HTTPClientError): + status_code = 416 + + +class HTTPExpectationFailed(HTTPClientError): + status_code = 417 + + +class HTTPMisdirectedRequest(HTTPClientError): + status_code = 421 + + +class HTTPUnprocessableEntity(HTTPClientError): + status_code = 422 + + +class HTTPFailedDependency(HTTPClientError): + status_code = 424 + + +class HTTPUpgradeRequired(HTTPClientError): + status_code = 426 + + +class HTTPPreconditionRequired(HTTPClientError): + status_code = 428 + + +class HTTPTooManyRequests(HTTPClientError): + status_code = 429 + + +class HTTPRequestHeaderFieldsTooLarge(HTTPClientError): + status_code = 431 + + +class HTTPUnavailableForLegalReasons(HTTPClientError): + status_code = 451 + + def __init__( + self, + link: str, + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + body: Any = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + super().__init__( + headers=headers, + reason=reason, + body=body, + text=text, + content_type=content_type, + ) + self.headers["Link"] = '<%s>; rel="blocked-by"' % link + self.link = link + + +############################################################ +# 5xx Server Error +############################################################ +# Response status codes beginning with the digit "5" indicate cases in +# which the server is aware that it has erred or is incapable of +# performing the request. Except when responding to a HEAD request, the +# server SHOULD include an entity containing an explanation of the error +# situation, and whether it is a temporary or permanent condition. User +# agents SHOULD display any included entity to the user. These response +# codes are applicable to any request method. + + +class HTTPServerError(HTTPError): + pass + + +class HTTPInternalServerError(HTTPServerError): + status_code = 500 + + +class HTTPNotImplemented(HTTPServerError): + status_code = 501 + + +class HTTPBadGateway(HTTPServerError): + status_code = 502 + + +class HTTPServiceUnavailable(HTTPServerError): + status_code = 503 + + +class HTTPGatewayTimeout(HTTPServerError): + status_code = 504 + + +class HTTPVersionNotSupported(HTTPServerError): + status_code = 505 + + +class HTTPVariantAlsoNegotiates(HTTPServerError): + status_code = 506 + + +class HTTPInsufficientStorage(HTTPServerError): + status_code = 507 + + +class HTTPNotExtended(HTTPServerError): + status_code = 510 + + +class HTTPNetworkAuthenticationRequired(HTTPServerError): + status_code = 511 diff --git a/lib/aiohttp/web_fileresponse.py b/lib/aiohttp/web_fileresponse.py new file mode 100644 index 0000000..f41ed3f --- /dev/null +++ b/lib/aiohttp/web_fileresponse.py @@ -0,0 +1,288 @@ +import asyncio +import mimetypes +import os +import pathlib +import sys +from typing import ( # noqa + IO, + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Iterator, + List, + Optional, + Tuple, + Union, + cast, +) + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import ETAG_ANY, ETag +from .typedefs import Final, LooseHeaders +from .web_exceptions import ( + HTTPNotModified, + HTTPPartialContent, + HTTPPreconditionFailed, + HTTPRequestRangeNotSatisfiable, +) +from .web_response import StreamResponse + +__all__ = ("FileResponse",) + +if TYPE_CHECKING: # pragma: no cover + from .web_request import BaseRequest + + +_T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] + + +NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE")) + + +class FileResponse(StreamResponse): + """A response object can be used to send files.""" + + def __init__( + self, + path: Union[str, pathlib.Path], + chunk_size: int = 256 * 1024, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + ) -> None: + super().__init__(status=status, reason=reason, headers=headers) + + if isinstance(path, str): + path = pathlib.Path(path) + + self._path = path + self._chunk_size = chunk_size + + async def _sendfile_fallback( + self, writer: AbstractStreamWriter, fobj: IO[Any], offset: int, count: int + ) -> AbstractStreamWriter: + # To keep memory usage low,fobj is transferred in chunks + # controlled by the constructor's chunk_size argument. + + chunk_size = self._chunk_size + loop = asyncio.get_event_loop() + + await loop.run_in_executor(None, fobj.seek, offset) + + chunk = await loop.run_in_executor(None, fobj.read, chunk_size) + while chunk: + await writer.write(chunk) + count = count - chunk_size + if count <= 0: + break + chunk = await loop.run_in_executor(None, fobj.read, min(chunk_size, count)) + + await writer.drain() + return writer + + async def _sendfile( + self, request: "BaseRequest", fobj: IO[Any], offset: int, count: int + ) -> AbstractStreamWriter: + writer = await super().prepare(request) + assert writer is not None + + if NOSENDFILE or sys.version_info < (3, 7) or self.compression: + return await self._sendfile_fallback(writer, fobj, offset, count) + + loop = request._loop + transport = request.transport + assert transport is not None + + try: + await loop.sendfile(transport, fobj, offset, count) + except NotImplementedError: + return await self._sendfile_fallback(writer, fobj, offset, count) + + await super().write_eof() + return writer + + @staticmethod + def _strong_etag_match(etag_value: str, etags: Tuple[ETag, ...]) -> bool: + if len(etags) == 1 and etags[0].value == ETAG_ANY: + return True + return any(etag.value == etag_value for etag in etags if not etag.is_weak) + + async def _not_modified( + self, request: "BaseRequest", etag_value: str, last_modified: float + ) -> Optional[AbstractStreamWriter]: + self.set_status(HTTPNotModified.status_code) + self._length_check = False + self.etag = etag_value # type: ignore[assignment] + self.last_modified = last_modified # type: ignore[assignment] + # Delete any Content-Length headers provided by user. HTTP 304 + # should always have empty response body + return await super().prepare(request) + + async def _precondition_failed( + self, request: "BaseRequest" + ) -> Optional[AbstractStreamWriter]: + self.set_status(HTTPPreconditionFailed.status_code) + self.content_length = 0 + return await super().prepare(request) + + async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]: + filepath = self._path + + gzip = False + if "gzip" in request.headers.get(hdrs.ACCEPT_ENCODING, ""): + gzip_path = filepath.with_name(filepath.name + ".gz") + + if gzip_path.is_file(): + filepath = gzip_path + gzip = True + + loop = asyncio.get_event_loop() + st: os.stat_result = await loop.run_in_executor(None, filepath.stat) + + etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}" + last_modified = st.st_mtime + + # https://tools.ietf.org/html/rfc7232#section-6 + ifmatch = request.if_match + if ifmatch is not None and not self._strong_etag_match(etag_value, ifmatch): + return await self._precondition_failed(request) + + unmodsince = request.if_unmodified_since + if ( + unmodsince is not None + and ifmatch is None + and st.st_mtime > unmodsince.timestamp() + ): + return await self._precondition_failed(request) + + ifnonematch = request.if_none_match + if ifnonematch is not None and self._strong_etag_match(etag_value, ifnonematch): + return await self._not_modified(request, etag_value, last_modified) + + modsince = request.if_modified_since + if ( + modsince is not None + and ifnonematch is None + and st.st_mtime <= modsince.timestamp() + ): + return await self._not_modified(request, etag_value, last_modified) + + if hdrs.CONTENT_TYPE not in self.headers: + ct, encoding = mimetypes.guess_type(str(filepath)) + if not ct: + ct = "application/octet-stream" + should_set_ct = True + else: + encoding = "gzip" if gzip else None + should_set_ct = False + + status = self._status + file_size = st.st_size + count = file_size + + start = None + + ifrange = request.if_range + if ifrange is None or st.st_mtime <= ifrange.timestamp(): + # If-Range header check: + # condition = cached date >= last modification date + # return 206 if True else 200. + # if False: + # Range header would not be processed, return 200 + # if True but Range header missing + # return 200 + try: + rng = request.http_range + start = rng.start + end = rng.stop + except ValueError: + # https://tools.ietf.org/html/rfc7233: + # A server generating a 416 (Range Not Satisfiable) response to + # a byte-range request SHOULD send a Content-Range header field + # with an unsatisfied-range value. + # The complete-length in a 416 response indicates the current + # length of the selected representation. + # + # Will do the same below. Many servers ignore this and do not + # send a Content-Range header with HTTP 416 + self.headers[hdrs.CONTENT_RANGE] = f"bytes */{file_size}" + self.set_status(HTTPRequestRangeNotSatisfiable.status_code) + return await super().prepare(request) + + # If a range request has been made, convert start, end slice + # notation into file pointer offset and count + if start is not None or end is not None: + if start < 0 and end is None: # return tail of file + start += file_size + if start < 0: + # if Range:bytes=-1000 in request header but file size + # is only 200, there would be trouble without this + start = 0 + count = file_size - start + else: + # rfc7233:If the last-byte-pos value is + # absent, or if the value is greater than or equal to + # the current length of the representation data, + # the byte range is interpreted as the remainder + # of the representation (i.e., the server replaces the + # value of last-byte-pos with a value that is one less than + # the current length of the selected representation). + count = ( + min(end if end is not None else file_size, file_size) - start + ) + + if start >= file_size: + # HTTP 416 should be returned in this case. + # + # According to https://tools.ietf.org/html/rfc7233: + # If a valid byte-range-set includes at least one + # byte-range-spec with a first-byte-pos that is less than + # the current length of the representation, or at least one + # suffix-byte-range-spec with a non-zero suffix-length, + # then the byte-range-set is satisfiable. Otherwise, the + # byte-range-set is unsatisfiable. + self.headers[hdrs.CONTENT_RANGE] = f"bytes */{file_size}" + self.set_status(HTTPRequestRangeNotSatisfiable.status_code) + return await super().prepare(request) + + status = HTTPPartialContent.status_code + # Even though you are sending the whole file, you should still + # return a HTTP 206 for a Range request. + self.set_status(status) + + if should_set_ct: + self.content_type = ct # type: ignore[assignment] + if encoding: + self.headers[hdrs.CONTENT_ENCODING] = encoding + if gzip: + self.headers[hdrs.VARY] = hdrs.ACCEPT_ENCODING + + self.etag = etag_value # type: ignore[assignment] + self.last_modified = st.st_mtime # type: ignore[assignment] + self.content_length = count + + self.headers[hdrs.ACCEPT_RANGES] = "bytes" + + real_start = cast(int, start) + + if status == HTTPPartialContent.status_code: + self.headers[hdrs.CONTENT_RANGE] = "bytes {}-{}/{}".format( + real_start, real_start + count - 1, file_size + ) + + # If we are sending 0 bytes calling sendfile() will throw a ValueError + if count == 0 or request.method == hdrs.METH_HEAD or self.status in [204, 304]: + return await super().prepare(request) + + fobj = await loop.run_in_executor(None, filepath.open, "rb") + if start: # be aware that start could be None or int=0 here. + offset = start + else: + offset = 0 + + try: + return await self._sendfile(request, fobj, offset, count) + finally: + await loop.run_in_executor(None, fobj.close) diff --git a/lib/aiohttp/web_log.py b/lib/aiohttp/web_log.py new file mode 100644 index 0000000..bc6e3b5 --- /dev/null +++ b/lib/aiohttp/web_log.py @@ -0,0 +1,208 @@ +import datetime +import functools +import logging +import os +import re +from collections import namedtuple +from typing import Any, Callable, Dict, Iterable, List, Tuple # noqa + +from .abc import AbstractAccessLogger +from .web_request import BaseRequest +from .web_response import StreamResponse + +KeyMethod = namedtuple("KeyMethod", "key method") + + +class AccessLogger(AbstractAccessLogger): + """Helper object to log access. + + Usage: + log = logging.getLogger("spam") + log_format = "%a %{User-Agent}i" + access_logger = AccessLogger(log, log_format) + access_logger.log(request, response, time) + + Format: + %% The percent sign + %a Remote IP-address (IP-address of proxy if using reverse proxy) + %t Time when the request was started to process + %P The process ID of the child that serviced the request + %r First line of request + %s Response status code + %b Size of response in bytes, including HTTP headers + %T Time taken to serve the request, in seconds + %Tf Time taken to serve the request, in seconds with floating fraction + in .06f format + %D Time taken to serve the request, in microseconds + %{FOO}i request.headers['FOO'] + %{FOO}o response.headers['FOO'] + %{FOO}e os.environ['FOO'] + + """ + + LOG_FORMAT_MAP = { + "a": "remote_address", + "t": "request_start_time", + "P": "process_id", + "r": "first_request_line", + "s": "response_status", + "b": "response_size", + "T": "request_time", + "Tf": "request_time_frac", + "D": "request_time_micro", + "i": "request_header", + "o": "response_header", + } + + LOG_FORMAT = '%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"' + FORMAT_RE = re.compile(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbOD]|Tf?)") + CLEANUP_RE = re.compile(r"(%[^s])") + _FORMAT_CACHE: Dict[str, Tuple[str, List[KeyMethod]]] = {} + + def __init__(self, logger: logging.Logger, log_format: str = LOG_FORMAT) -> None: + """Initialise the logger. + + logger is a logger object to be used for logging. + log_format is a string with apache compatible log format description. + + """ + super().__init__(logger, log_format=log_format) + + _compiled_format = AccessLogger._FORMAT_CACHE.get(log_format) + if not _compiled_format: + _compiled_format = self.compile_format(log_format) + AccessLogger._FORMAT_CACHE[log_format] = _compiled_format + + self._log_format, self._methods = _compiled_format + + def compile_format(self, log_format: str) -> Tuple[str, List[KeyMethod]]: + """Translate log_format into form usable by modulo formatting + + All known atoms will be replaced with %s + Also methods for formatting of those atoms will be added to + _methods in appropriate order + + For example we have log_format = "%a %t" + This format will be translated to "%s %s" + Also contents of _methods will be + [self._format_a, self._format_t] + These method will be called and results will be passed + to translated string format. + + Each _format_* method receive 'args' which is list of arguments + given to self.log + + Exceptions are _format_e, _format_i and _format_o methods which + also receive key name (by functools.partial) + + """ + # list of (key, method) tuples, we don't use an OrderedDict as users + # can repeat the same key more than once + methods = list() + + for atom in self.FORMAT_RE.findall(log_format): + if atom[1] == "": + format_key1 = self.LOG_FORMAT_MAP[atom[0]] + m = getattr(AccessLogger, "_format_%s" % atom[0]) + key_method = KeyMethod(format_key1, m) + else: + format_key2 = (self.LOG_FORMAT_MAP[atom[2]], atom[1]) + m = getattr(AccessLogger, "_format_%s" % atom[2]) + key_method = KeyMethod(format_key2, functools.partial(m, atom[1])) + + methods.append(key_method) + + log_format = self.FORMAT_RE.sub(r"%s", log_format) + log_format = self.CLEANUP_RE.sub(r"%\1", log_format) + return log_format, methods + + @staticmethod + def _format_i( + key: str, request: BaseRequest, response: StreamResponse, time: float + ) -> str: + if request is None: + return "(no headers)" + + # suboptimal, make istr(key) once + return request.headers.get(key, "-") + + @staticmethod + def _format_o( + key: str, request: BaseRequest, response: StreamResponse, time: float + ) -> str: + # suboptimal, make istr(key) once + return response.headers.get(key, "-") + + @staticmethod + def _format_a(request: BaseRequest, response: StreamResponse, time: float) -> str: + if request is None: + return "-" + ip = request.remote + return ip if ip is not None else "-" + + @staticmethod + def _format_t(request: BaseRequest, response: StreamResponse, time: float) -> str: + now = datetime.datetime.utcnow() + start_time = now - datetime.timedelta(seconds=time) + return start_time.strftime("[%d/%b/%Y:%H:%M:%S +0000]") + + @staticmethod + def _format_P(request: BaseRequest, response: StreamResponse, time: float) -> str: + return "<%s>" % os.getpid() + + @staticmethod + def _format_r(request: BaseRequest, response: StreamResponse, time: float) -> str: + if request is None: + return "-" + return "{} {} HTTP/{}.{}".format( + request.method, + request.path_qs, + request.version.major, + request.version.minor, + ) + + @staticmethod + def _format_s(request: BaseRequest, response: StreamResponse, time: float) -> int: + return response.status + + @staticmethod + def _format_b(request: BaseRequest, response: StreamResponse, time: float) -> int: + return response.body_length + + @staticmethod + def _format_T(request: BaseRequest, response: StreamResponse, time: float) -> str: + return str(round(time)) + + @staticmethod + def _format_Tf(request: BaseRequest, response: StreamResponse, time: float) -> str: + return "%06f" % time + + @staticmethod + def _format_D(request: BaseRequest, response: StreamResponse, time: float) -> str: + return str(round(time * 1000000)) + + def _format_line( + self, request: BaseRequest, response: StreamResponse, time: float + ) -> Iterable[Tuple[str, Callable[[BaseRequest, StreamResponse, float], str]]]: + return [(key, method(request, response, time)) for key, method in self._methods] + + def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None: + try: + fmt_info = self._format_line(request, response, time) + + values = list() + extra = dict() + for key, value in fmt_info: + values.append(value) + + if key.__class__ is str: + extra[key] = value + else: + k1, k2 = key # type: ignore[misc] + dct = extra.get(k1, {}) # type: ignore[var-annotated,has-type] + dct[k2] = value # type: ignore[index,has-type] + extra[k1] = dct # type: ignore[has-type,assignment] + + self.logger.info(self._log_format % tuple(values), extra=extra) + except Exception: + self.logger.exception("Error in logging") diff --git a/lib/aiohttp/web_middlewares.py b/lib/aiohttp/web_middlewares.py new file mode 100644 index 0000000..fabcc44 --- /dev/null +++ b/lib/aiohttp/web_middlewares.py @@ -0,0 +1,119 @@ +import re +from typing import TYPE_CHECKING, Awaitable, Callable, Tuple, Type, TypeVar + +from .typedefs import Handler +from .web_exceptions import HTTPPermanentRedirect, _HTTPMove +from .web_request import Request +from .web_response import StreamResponse +from .web_urldispatcher import SystemRoute + +__all__ = ( + "middleware", + "normalize_path_middleware", +) + +if TYPE_CHECKING: # pragma: no cover + from .web_app import Application + +_Func = TypeVar("_Func") + + +async def _check_request_resolves(request: Request, path: str) -> Tuple[bool, Request]: + alt_request = request.clone(rel_url=path) + + match_info = await request.app.router.resolve(alt_request) + alt_request._match_info = match_info + + if match_info.http_exception is None: + return True, alt_request + + return False, request + + +def middleware(f: _Func) -> _Func: + f.__middleware_version__ = 1 # type: ignore[attr-defined] + return f + + +_Middleware = Callable[[Request, Handler], Awaitable[StreamResponse]] + + +def normalize_path_middleware( + *, + append_slash: bool = True, + remove_slash: bool = False, + merge_slashes: bool = True, + redirect_class: Type[_HTTPMove] = HTTPPermanentRedirect, +) -> _Middleware: + """Factory for producing a middleware that normalizes the path of a request. + + Normalizing means: + - Add or remove a trailing slash to the path. + - Double slashes are replaced by one. + + The middleware returns as soon as it finds a path that resolves + correctly. The order if both merge and append/remove are enabled is + 1) merge slashes + 2) append/remove slash + 3) both merge slashes and append/remove slash. + If the path resolves with at least one of those conditions, it will + redirect to the new path. + + Only one of `append_slash` and `remove_slash` can be enabled. If both + are `True` the factory will raise an assertion error + + If `append_slash` is `True` the middleware will append a slash when + needed. If a resource is defined with trailing slash and the request + comes without it, it will append it automatically. + + If `remove_slash` is `True`, `append_slash` must be `False`. When enabled + the middleware will remove trailing slashes and redirect if the resource + is defined + + If merge_slashes is True, merge multiple consecutive slashes in the + path into one. + """ + correct_configuration = not (append_slash and remove_slash) + assert correct_configuration, "Cannot both remove and append slash" + + @middleware + async def impl(request: Request, handler: Handler) -> StreamResponse: + if isinstance(request.match_info.route, SystemRoute): + paths_to_check = [] + if "?" in request.raw_path: + path, query = request.raw_path.split("?", 1) + query = "?" + query + else: + query = "" + path = request.raw_path + + if merge_slashes: + paths_to_check.append(re.sub("//+", "/", path)) + if append_slash and not request.path.endswith("/"): + paths_to_check.append(path + "/") + if remove_slash and request.path.endswith("/"): + paths_to_check.append(path[:-1]) + if merge_slashes and append_slash: + paths_to_check.append(re.sub("//+", "/", path + "/")) + if merge_slashes and remove_slash: + merged_slashes = re.sub("//+", "/", path) + paths_to_check.append(merged_slashes[:-1]) + + for path in paths_to_check: + path = re.sub("^//+", "/", path) # SECURITY: GHSA-v6wp-4m6f-gcjg + resolves, request = await _check_request_resolves(request, path) + if resolves: + raise redirect_class(request.raw_path + query) + + return await handler(request) + + return impl + + +def _fix_request_current_app(app: "Application") -> _Middleware: + @middleware + async def impl(request: Request, handler: Handler) -> StreamResponse: + with request.match_info.set_current_app(app): + return await handler(request) + + return impl diff --git a/lib/aiohttp/web_protocol.py b/lib/aiohttp/web_protocol.py new file mode 100644 index 0000000..10a9608 --- /dev/null +++ b/lib/aiohttp/web_protocol.py @@ -0,0 +1,679 @@ +import asyncio +import asyncio.streams +import traceback +import warnings +from collections import deque +from contextlib import suppress +from html import escape as html_escape +from http import HTTPStatus +from logging import Logger +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Deque, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) + +import attr +import yarl + +from .abc import AbstractAccessLogger, AbstractStreamWriter +from .base_protocol import BaseProtocol +from .helpers import ceil_timeout +from .http import ( + HttpProcessingError, + HttpRequestParser, + HttpVersion10, + RawRequestMessage, + StreamWriter, +) +from .log import access_logger, server_logger +from .streams import EMPTY_PAYLOAD, StreamReader +from .tcp_helpers import tcp_keepalive +from .web_exceptions import HTTPException +from .web_log import AccessLogger +from .web_request import BaseRequest +from .web_response import Response, StreamResponse + +__all__ = ("RequestHandler", "RequestPayloadError", "PayloadAccessError") + +if TYPE_CHECKING: # pragma: no cover + from .web_server import Server + + +_RequestFactory = Callable[ + [ + RawRequestMessage, + StreamReader, + "RequestHandler", + AbstractStreamWriter, + "asyncio.Task[None]", + ], + BaseRequest, +] + +_RequestHandler = Callable[[BaseRequest], Awaitable[StreamResponse]] + +ERROR = RawRequestMessage( + "UNKNOWN", + "/", + HttpVersion10, + {}, # type: ignore[arg-type] + {}, # type: ignore[arg-type] + True, + None, + False, + False, + yarl.URL("/"), +) + + +class RequestPayloadError(Exception): + """Payload parsing error.""" + + +class PayloadAccessError(Exception): + """Payload was accessed after response was sent.""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class _ErrInfo: + status: int + exc: BaseException + message: str + + +_MsgType = Tuple[Union[RawRequestMessage, _ErrInfo], StreamReader] + + +class RequestHandler(BaseProtocol): + """HTTP protocol implementation. + + RequestHandler handles incoming HTTP request. It reads request line, + request headers and request payload and calls handle_request() method. + By default it always returns with 404 response. + + RequestHandler handles errors in incoming request, like bad + status line, bad headers or incomplete payload. If any error occurs, + connection gets closed. + + keepalive_timeout -- number of seconds before closing + keep-alive connection + + tcp_keepalive -- TCP keep-alive is on, default is on + + debug -- enable debug mode + + logger -- custom logger object + + access_log_class -- custom class for access_logger + + access_log -- custom logging object + + access_log_format -- access log format string + + loop -- Optional event loop + + max_line_size -- Optional maximum header line size + + max_field_size -- Optional maximum header field size + + max_headers -- Optional maximum header size + + """ + + KEEPALIVE_RESCHEDULE_DELAY = 1 + + __slots__ = ( + "_request_count", + "_keepalive", + "_manager", + "_request_handler", + "_request_factory", + "_tcp_keepalive", + "_keepalive_time", + "_keepalive_handle", + "_keepalive_timeout", + "_lingering_time", + "_messages", + "_message_tail", + "_waiter", + "_task_handler", + "_upgrade", + "_payload_parser", + "_request_parser", + "_reading_paused", + "logger", + "debug", + "access_log", + "access_logger", + "_close", + "_force_close", + "_current_request", + ) + + def __init__( + self, + manager: "Server", + *, + loop: asyncio.AbstractEventLoop, + keepalive_timeout: float = 75.0, # NGINX default is 75 secs + tcp_keepalive: bool = True, + logger: Logger = server_logger, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log: Logger = access_logger, + access_log_format: str = AccessLogger.LOG_FORMAT, + debug: bool = False, + max_line_size: int = 8190, + max_headers: int = 32768, + max_field_size: int = 8190, + lingering_time: float = 10.0, + read_bufsize: int = 2**16, + auto_decompress: bool = True, + ): + super().__init__(loop) + + self._request_count = 0 + self._keepalive = False + self._current_request: Optional[BaseRequest] = None + self._manager: Optional[Server] = manager + self._request_handler: Optional[_RequestHandler] = manager.request_handler + self._request_factory: Optional[_RequestFactory] = manager.request_factory + + self._tcp_keepalive = tcp_keepalive + # placeholder to be replaced on keepalive timeout setup + self._keepalive_time = 0.0 + self._keepalive_handle: Optional[asyncio.Handle] = None + self._keepalive_timeout = keepalive_timeout + self._lingering_time = float(lingering_time) + + self._messages: Deque[_MsgType] = deque() + self._message_tail = b"" + + self._waiter: Optional[asyncio.Future[None]] = None + self._task_handler: Optional[asyncio.Task[None]] = None + + self._upgrade = False + self._payload_parser: Any = None + self._request_parser: Optional[HttpRequestParser] = HttpRequestParser( + self, + loop, + read_bufsize, + max_line_size=max_line_size, + max_field_size=max_field_size, + max_headers=max_headers, + payload_exception=RequestPayloadError, + auto_decompress=auto_decompress, + ) + + self.logger = logger + self.debug = debug + self.access_log = access_log + if access_log: + self.access_logger: Optional[AbstractAccessLogger] = access_log_class( + access_log, access_log_format + ) + else: + self.access_logger = None + + self._close = False + self._force_close = False + + def __repr__(self) -> str: + return "<{} {}>".format( + self.__class__.__name__, + "connected" if self.transport is not None else "disconnected", + ) + + @property + def keepalive_timeout(self) -> float: + return self._keepalive_timeout + + async def shutdown(self, timeout: Optional[float] = 15.0) -> None: + """Do worker process exit preparations. + + We need to clean up everything and stop accepting requests. + It is especially important for keep-alive connections. + """ + self._force_close = True + + if self._keepalive_handle is not None: + self._keepalive_handle.cancel() + + if self._waiter: + self._waiter.cancel() + + # wait for handlers + with suppress(asyncio.CancelledError, asyncio.TimeoutError): + async with ceil_timeout(timeout): + if self._current_request is not None: + self._current_request._cancel(asyncio.CancelledError()) + + if self._task_handler is not None and not self._task_handler.done(): + await self._task_handler + + # force-close non-idle handler + if self._task_handler is not None: + self._task_handler.cancel() + + if self.transport is not None: + self.transport.close() + self.transport = None + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + super().connection_made(transport) + + real_transport = cast(asyncio.Transport, transport) + if self._tcp_keepalive: + tcp_keepalive(real_transport) + + self._task_handler = self._loop.create_task(self.start()) + assert self._manager is not None + self._manager.connection_made(self, real_transport) + + def connection_lost(self, exc: Optional[BaseException]) -> None: + if self._manager is None: + return + self._manager.connection_lost(self, exc) + + super().connection_lost(exc) + + self._manager = None + self._force_close = True + self._request_factory = None + self._request_handler = None + self._request_parser = None + + if self._keepalive_handle is not None: + self._keepalive_handle.cancel() + + if self._current_request is not None: + if exc is None: + exc = ConnectionResetError("Connection lost") + self._current_request._cancel(exc) + + if self._waiter is not None: + self._waiter.cancel() + + self._task_handler = None + + if self._payload_parser is not None: + self._payload_parser.feed_eof() + self._payload_parser = None + + def set_parser(self, parser: Any) -> None: + # Actual type is WebReader + assert self._payload_parser is None + + self._payload_parser = parser + + if self._message_tail: + self._payload_parser.feed_data(self._message_tail) + self._message_tail = b"" + + def eof_received(self) -> None: + pass + + def data_received(self, data: bytes) -> None: + if self._force_close or self._close: + return + # parse http messages + messages: Sequence[_MsgType] + if self._payload_parser is None and not self._upgrade: + assert self._request_parser is not None + try: + messages, upgraded, tail = self._request_parser.feed_data(data) + except HttpProcessingError as exc: + messages = [ + (_ErrInfo(status=400, exc=exc, message=exc.message), EMPTY_PAYLOAD) + ] + upgraded = False + tail = b"" + + for msg, payload in messages or (): + self._request_count += 1 + self._messages.append((msg, payload)) + + waiter = self._waiter + if messages and waiter is not None and not waiter.done(): + # don't set result twice + waiter.set_result(None) + + self._upgrade = upgraded + if upgraded and tail: + self._message_tail = tail + + # no parser, just store + elif self._payload_parser is None and self._upgrade and data: + self._message_tail += data + + # feed payload + elif data: + eof, tail = self._payload_parser.feed_data(data) + if eof: + self.close() + + def keep_alive(self, val: bool) -> None: + """Set keep-alive connection mode. + + :param bool val: new state. + """ + self._keepalive = val + if self._keepalive_handle: + self._keepalive_handle.cancel() + self._keepalive_handle = None + + def close(self) -> None: + """Close connection. + + Stop accepting new pipelining messages and close + connection when handlers done processing messages. + """ + self._close = True + if self._waiter: + self._waiter.cancel() + + def force_close(self) -> None: + """Forcefully close connection.""" + self._force_close = True + if self._waiter: + self._waiter.cancel() + if self.transport is not None: + self.transport.close() + self.transport = None + + def log_access( + self, request: BaseRequest, response: StreamResponse, time: float + ) -> None: + if self.access_logger is not None: + self.access_logger.log(request, response, self._loop.time() - time) + + def log_debug(self, *args: Any, **kw: Any) -> None: + if self.debug: + self.logger.debug(*args, **kw) + + def log_exception(self, *args: Any, **kw: Any) -> None: + self.logger.exception(*args, **kw) + + def _process_keepalive(self) -> None: + if self._force_close or not self._keepalive: + return + + next = self._keepalive_time + self._keepalive_timeout + + # handler in idle state + if self._waiter: + if self._loop.time() > next: + self.force_close() + return + + # not all request handlers are done, + # reschedule itself to next second + self._keepalive_handle = self._loop.call_later( + self.KEEPALIVE_RESCHEDULE_DELAY, self._process_keepalive + ) + + async def _handle_request( + self, + request: BaseRequest, + start_time: float, + request_handler: Callable[[BaseRequest], Awaitable[StreamResponse]], + ) -> Tuple[StreamResponse, bool]: + assert self._request_handler is not None + try: + try: + self._current_request = request + resp = await request_handler(request) + finally: + self._current_request = None + except HTTPException as exc: + resp = exc + reset = await self.finish_response(request, resp, start_time) + except asyncio.CancelledError: + raise + except asyncio.TimeoutError as exc: + self.log_debug("Request handler timed out.", exc_info=exc) + resp = self.handle_error(request, 504) + reset = await self.finish_response(request, resp, start_time) + except Exception as exc: + resp = self.handle_error(request, 500, exc) + reset = await self.finish_response(request, resp, start_time) + else: + # Deprecation warning (See #2415) + if getattr(resp, "__http_exception__", False): + warnings.warn( + "returning HTTPException object is deprecated " + "(#2415) and will be removed, " + "please raise the exception instead", + DeprecationWarning, + ) + + reset = await self.finish_response(request, resp, start_time) + + return resp, reset + + async def start(self) -> None: + """Process incoming request. + + It reads request line, request headers and request payload, then + calls handle_request() method. Subclass has to override + handle_request(). start() handles various exceptions in request + or response handling. Connection is being closed always unless + keep_alive(True) specified. + """ + loop = self._loop + handler = self._task_handler + assert handler is not None + manager = self._manager + assert manager is not None + keepalive_timeout = self._keepalive_timeout + resp = None + assert self._request_factory is not None + assert self._request_handler is not None + + while not self._force_close: + if not self._messages: + try: + # wait for next request + self._waiter = loop.create_future() + await self._waiter + except asyncio.CancelledError: + break + finally: + self._waiter = None + + message, payload = self._messages.popleft() + + start = loop.time() + + manager.requests_count += 1 + writer = StreamWriter(self, loop) + if isinstance(message, _ErrInfo): + # make request_factory work + request_handler = self._make_error_handler(message) + message = ERROR + else: + request_handler = self._request_handler + + request = self._request_factory(message, payload, self, writer, handler) + try: + # a new task is used for copy context vars (#3406) + task = self._loop.create_task( + self._handle_request(request, start, request_handler) + ) + try: + resp, reset = await task + except (asyncio.CancelledError, ConnectionError): + self.log_debug("Ignored premature client disconnection") + break + + # Drop the processed task from asyncio.Task.all_tasks() early + del task + if reset: + self.log_debug("Ignored premature client disconnection 2") + break + + # notify server about keep-alive + self._keepalive = bool(resp.keep_alive) + + # check payload + if not payload.is_eof(): + lingering_time = self._lingering_time + if not self._force_close and lingering_time: + self.log_debug( + "Start lingering close timer for %s sec.", lingering_time + ) + + now = loop.time() + end_t = now + lingering_time + + with suppress(asyncio.TimeoutError, asyncio.CancelledError): + while not payload.is_eof() and now < end_t: + async with ceil_timeout(end_t - now): + # read and ignore + await payload.readany() + now = loop.time() + + # if payload still uncompleted + if not payload.is_eof() and not self._force_close: + self.log_debug("Uncompleted request.") + self.close() + + payload.set_exception(PayloadAccessError()) + + except asyncio.CancelledError: + self.log_debug("Ignored premature client disconnection ") + break + except RuntimeError as exc: + if self.debug: + self.log_exception("Unhandled runtime exception", exc_info=exc) + self.force_close() + except Exception as exc: + self.log_exception("Unhandled exception", exc_info=exc) + self.force_close() + finally: + if self.transport is None and resp is not None: + self.log_debug("Ignored premature client disconnection.") + elif not self._force_close: + if self._keepalive and not self._close: + # start keep-alive timer + if keepalive_timeout is not None: + now = self._loop.time() + self._keepalive_time = now + if self._keepalive_handle is None: + self._keepalive_handle = loop.call_at( + now + keepalive_timeout, self._process_keepalive + ) + else: + break + + # remove handler, close transport if no handlers left + if not self._force_close: + self._task_handler = None + if self.transport is not None: + self.transport.close() + + async def finish_response( + self, request: BaseRequest, resp: StreamResponse, start_time: float + ) -> bool: + """Prepare the response and write_eof, then log access. + + This has to + be called within the context of any exception so the access logger + can get exception information. Returns True if the client disconnects + prematurely. + """ + if self._request_parser is not None: + self._request_parser.set_upgraded(False) + self._upgrade = False + if self._message_tail: + self._request_parser.feed_data(self._message_tail) + self._message_tail = b"" + try: + prepare_meth = resp.prepare + except AttributeError: + if resp is None: + raise RuntimeError("Missing return " "statement on request handler") + else: + raise RuntimeError( + "Web-handler should return " + "a response instance, " + "got {!r}".format(resp) + ) + try: + await prepare_meth(request) + await resp.write_eof() + except ConnectionError: + self.log_access(request, resp, start_time) + return True + else: + self.log_access(request, resp, start_time) + return False + + def handle_error( + self, + request: BaseRequest, + status: int = 500, + exc: Optional[BaseException] = None, + message: Optional[str] = None, + ) -> StreamResponse: + """Handle errors. + + Returns HTTP response with specific status code. Logs additional + information. It always closes current connection. + """ + self.log_exception("Error handling request", exc_info=exc) + + # some data already got sent, connection is broken + if request.writer.output_size > 0: + raise ConnectionError( + "Response is sent already, cannot send another response " + "with the error message" + ) + + ct = "text/plain" + if status == HTTPStatus.INTERNAL_SERVER_ERROR: + title = "{0.value} {0.phrase}".format(HTTPStatus.INTERNAL_SERVER_ERROR) + msg = HTTPStatus.INTERNAL_SERVER_ERROR.description + tb = None + if self.debug: + with suppress(Exception): + tb = traceback.format_exc() + + if "text/html" in request.headers.get("Accept", ""): + if tb: + tb = html_escape(tb) + msg = f"

Traceback:

\n
{tb}
" + message = ( + "" + "{title}" + "\n

{title}

" + "\n{msg}\n\n" + ).format(title=title, msg=msg) + ct = "text/html" + else: + if tb: + msg = tb + message = title + "\n\n" + msg + + resp = Response(status=status, text=message, content_type=ct) + resp.force_close() + + return resp + + def _make_error_handler( + self, err_info: _ErrInfo + ) -> Callable[[BaseRequest], Awaitable[StreamResponse]]: + async def handler(request: BaseRequest) -> StreamResponse: + return self.handle_error( + request, err_info.status, err_info.exc, err_info.message + ) + + return handler diff --git a/lib/aiohttp/web_request.py b/lib/aiohttp/web_request.py new file mode 100644 index 0000000..c02ebfc --- /dev/null +++ b/lib/aiohttp/web_request.py @@ -0,0 +1,882 @@ +import asyncio +import datetime +import io +import re +import socket +import string +import tempfile +import types +import warnings +from http.cookies import SimpleCookie +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Iterator, + Mapping, + MutableMapping, + Optional, + Pattern, + Tuple, + Union, + cast, +) +from urllib.parse import parse_qsl + +import attr +from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy +from yarl import URL + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import ( + DEBUG, + ETAG_ANY, + LIST_QUOTED_ETAG_RE, + ChainMapProxy, + ETag, + HeadersMixin, + parse_http_date, + reify, + sentinel, +) +from .http_parser import RawRequestMessage +from .http_writer import HttpVersion +from .multipart import BodyPartReader, MultipartReader +from .streams import EmptyStreamReader, StreamReader +from .typedefs import ( + DEFAULT_JSON_DECODER, + Final, + JSONDecoder, + LooseHeaders, + RawHeaders, + StrOrURL, +) +from .web_exceptions import HTTPRequestEntityTooLarge +from .web_response import StreamResponse + +__all__ = ("BaseRequest", "FileField", "Request") + + +if TYPE_CHECKING: # pragma: no cover + from .web_app import Application + from .web_protocol import RequestHandler + from .web_urldispatcher import UrlMappingMatchInfo + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class FileField: + name: str + filename: str + file: io.BufferedReader + content_type: str + headers: "CIMultiDictProxy[str]" + + +_TCHAR: Final[str] = string.digits + string.ascii_letters + r"!#$%&'*+.^_`|~-" +# '-' at the end to prevent interpretation as range in a char class + +_TOKEN: Final[str] = rf"[{_TCHAR}]+" + +_QDTEXT: Final[str] = r"[{}]".format( + r"".join(chr(c) for c in (0x09, 0x20, 0x21) + tuple(range(0x23, 0x7F))) +) +# qdtext includes 0x5C to escape 0x5D ('\]') +# qdtext excludes obs-text (because obsoleted, and encoding not specified) + +_QUOTED_PAIR: Final[str] = r"\\[\t !-~]" + +_QUOTED_STRING: Final[str] = r'"(?:{quoted_pair}|{qdtext})*"'.format( + qdtext=_QDTEXT, quoted_pair=_QUOTED_PAIR +) + +_FORWARDED_PAIR: Final[ + str +] = r"({token})=({token}|{quoted_string})(:\d{{1,4}})?".format( + token=_TOKEN, quoted_string=_QUOTED_STRING +) + +_QUOTED_PAIR_REPLACE_RE: Final[Pattern[str]] = re.compile(r"\\([\t !-~])") +# same pattern as _QUOTED_PAIR but contains a capture group + +_FORWARDED_PAIR_RE: Final[Pattern[str]] = re.compile(_FORWARDED_PAIR) + +############################################################ +# HTTP Request +############################################################ + + +class BaseRequest(MutableMapping[str, Any], HeadersMixin): + + POST_METHODS = { + hdrs.METH_PATCH, + hdrs.METH_POST, + hdrs.METH_PUT, + hdrs.METH_TRACE, + hdrs.METH_DELETE, + } + + ATTRS = HeadersMixin.ATTRS | frozenset( + [ + "_message", + "_protocol", + "_payload_writer", + "_payload", + "_headers", + "_method", + "_version", + "_rel_url", + "_post", + "_read_bytes", + "_state", + "_cache", + "_task", + "_client_max_size", + "_loop", + "_transport_sslcontext", + "_transport_peername", + ] + ) + + def __init__( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: "RequestHandler", + payload_writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + loop: asyncio.AbstractEventLoop, + *, + client_max_size: int = 1024**2, + state: Optional[Dict[str, Any]] = None, + scheme: Optional[str] = None, + host: Optional[str] = None, + remote: Optional[str] = None, + ) -> None: + if state is None: + state = {} + self._message = message + self._protocol = protocol + self._payload_writer = payload_writer + + self._payload = payload + self._headers = message.headers + self._method = message.method + self._version = message.version + self._cache: Dict[str, Any] = {} + url = message.url + if url.is_absolute(): + # absolute URL is given, + # override auto-calculating url, host, and scheme + # all other properties should be good + self._cache["url"] = url + self._cache["host"] = url.host + self._cache["scheme"] = url.scheme + self._rel_url = url.relative() + else: + self._rel_url = message.url + self._post: Optional[MultiDictProxy[Union[str, bytes, FileField]]] = None + self._read_bytes: Optional[bytes] = None + + self._state = state + self._task = task + self._client_max_size = client_max_size + self._loop = loop + + transport = self._protocol.transport + assert transport is not None + self._transport_sslcontext = transport.get_extra_info("sslcontext") + self._transport_peername = transport.get_extra_info("peername") + + if scheme is not None: + self._cache["scheme"] = scheme + if host is not None: + self._cache["host"] = host + if remote is not None: + self._cache["remote"] = remote + + def clone( + self, + *, + method: str = sentinel, + rel_url: StrOrURL = sentinel, + headers: LooseHeaders = sentinel, + scheme: str = sentinel, + host: str = sentinel, + remote: str = sentinel, + ) -> "BaseRequest": + """Clone itself with replacement some attributes. + + Creates and returns a new instance of Request object. If no parameters + are given, an exact copy is returned. If a parameter is not passed, it + will reuse the one from the current request object. + """ + if self._read_bytes: + raise RuntimeError("Cannot clone request " "after reading its content") + + dct: Dict[str, Any] = {} + if method is not sentinel: + dct["method"] = method + if rel_url is not sentinel: + new_url = URL(rel_url) + dct["url"] = new_url + dct["path"] = str(new_url) + if headers is not sentinel: + # a copy semantic + dct["headers"] = CIMultiDictProxy(CIMultiDict(headers)) + dct["raw_headers"] = tuple( + (k.encode("utf-8"), v.encode("utf-8")) for k, v in headers.items() + ) + + message = self._message._replace(**dct) + + kwargs = {} + if scheme is not sentinel: + kwargs["scheme"] = scheme + if host is not sentinel: + kwargs["host"] = host + if remote is not sentinel: + kwargs["remote"] = remote + + return self.__class__( + message, + self._payload, + self._protocol, + self._payload_writer, + self._task, + self._loop, + client_max_size=self._client_max_size, + state=self._state.copy(), + **kwargs, + ) + + @property + def task(self) -> "asyncio.Task[None]": + return self._task + + @property + def protocol(self) -> "RequestHandler": + return self._protocol + + @property + def transport(self) -> Optional[asyncio.Transport]: + if self._protocol is None: + return None + return self._protocol.transport + + @property + def writer(self) -> AbstractStreamWriter: + return self._payload_writer + + @reify + def message(self) -> RawRequestMessage: + warnings.warn("Request.message is deprecated", DeprecationWarning, stacklevel=3) + return self._message + + @reify + def rel_url(self) -> URL: + return self._rel_url + + @reify + def loop(self) -> asyncio.AbstractEventLoop: + warnings.warn( + "request.loop property is deprecated", DeprecationWarning, stacklevel=2 + ) + return self._loop + + # MutableMapping API + + def __getitem__(self, key: str) -> Any: + return self._state[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._state[key] = value + + def __delitem__(self, key: str) -> None: + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[str]: + return iter(self._state) + + ######## + + @reify + def secure(self) -> bool: + """A bool indicating if the request is handled with SSL.""" + return self.scheme == "https" + + @reify + def forwarded(self) -> Tuple[Mapping[str, str], ...]: + """A tuple containing all parsed Forwarded header(s). + + Makes an effort to parse Forwarded headers as specified by RFC 7239: + + - It adds one (immutable) dictionary per Forwarded 'field-value', ie + per proxy. The element corresponds to the data in the Forwarded + field-value added by the first proxy encountered by the client. Each + subsequent item corresponds to those added by later proxies. + - It checks that every value has valid syntax in general as specified + in section 4: either a 'token' or a 'quoted-string'. + - It un-escapes found escape sequences. + - It does NOT validate 'by' and 'for' contents as specified in section + 6. + - It does NOT validate 'host' contents (Host ABNF). + - It does NOT validate 'proto' contents for valid URI scheme names. + + Returns a tuple containing one or more immutable dicts + """ + elems = [] + for field_value in self._message.headers.getall(hdrs.FORWARDED, ()): + length = len(field_value) + pos = 0 + need_separator = False + elem: Dict[str, str] = {} + elems.append(types.MappingProxyType(elem)) + while 0 <= pos < length: + match = _FORWARDED_PAIR_RE.match(field_value, pos) + if match is not None: # got a valid forwarded-pair + if need_separator: + # bad syntax here, skip to next comma + pos = field_value.find(",", pos) + else: + name, value, port = match.groups() + if value[0] == '"': + # quoted string: remove quotes and unescape + value = _QUOTED_PAIR_REPLACE_RE.sub(r"\1", value[1:-1]) + if port: + value += port + elem[name.lower()] = value + pos += len(match.group(0)) + need_separator = True + elif field_value[pos] == ",": # next forwarded-element + need_separator = False + elem = {} + elems.append(types.MappingProxyType(elem)) + pos += 1 + elif field_value[pos] == ";": # next forwarded-pair + need_separator = False + pos += 1 + elif field_value[pos] in " \t": + # Allow whitespace even between forwarded-pairs, though + # RFC 7239 doesn't. This simplifies code and is in line + # with Postel's law. + pos += 1 + else: + # bad syntax here, skip to next comma + pos = field_value.find(",", pos) + return tuple(elems) + + @reify + def scheme(self) -> str: + """A string representing the scheme of the request. + + Hostname is resolved in this order: + + - overridden value by .clone(scheme=new_scheme) call. + - type of connection to peer: HTTPS if socket is SSL, HTTP otherwise. + + 'http' or 'https'. + """ + if self._transport_sslcontext: + return "https" + else: + return "http" + + @reify + def method(self) -> str: + """Read only property for getting HTTP method. + + The value is upper-cased str like 'GET', 'POST', 'PUT' etc. + """ + return self._method + + @reify + def version(self) -> HttpVersion: + """Read only property for getting HTTP version of request. + + Returns aiohttp.protocol.HttpVersion instance. + """ + return self._version + + @reify + def host(self) -> str: + """Hostname of the request. + + Hostname is resolved in this order: + + - overridden value by .clone(host=new_host) call. + - HOST HTTP header + - socket.getfqdn() value + """ + host = self._message.headers.get(hdrs.HOST) + if host is not None: + return host + return socket.getfqdn() + + @reify + def remote(self) -> Optional[str]: + """Remote IP of client initiated HTTP request. + + The IP is resolved in this order: + + - overridden value by .clone(remote=new_remote) call. + - peername of opened socket + """ + if self._transport_peername is None: + return None + if isinstance(self._transport_peername, (list, tuple)): + return str(self._transport_peername[0]) + return str(self._transport_peername) + + @reify + def url(self) -> URL: + url = URL.build(scheme=self.scheme, host=self.host) + return url.join(self._rel_url) + + @reify + def path(self) -> str: + """The URL including *PATH INFO* without the host or scheme. + + E.g., ``/app/blog`` + """ + return self._rel_url.path + + @reify + def path_qs(self) -> str: + """The URL including PATH_INFO and the query string. + + E.g, /app/blog?id=10 + """ + return str(self._rel_url) + + @reify + def raw_path(self) -> str: + """The URL including raw *PATH INFO* without the host or scheme. + + Warning, the path is unquoted and may contains non valid URL characters + + E.g., ``/my%2Fpath%7Cwith%21some%25strange%24characters`` + """ + return self._message.path + + @reify + def query(self) -> "MultiDictProxy[str]": + """A multidict with all the variables in the query string.""" + return MultiDictProxy(self._rel_url.query) + + @reify + def query_string(self) -> str: + """The query string in the URL. + + E.g., id=10 + """ + return self._rel_url.query_string + + @reify + def headers(self) -> "CIMultiDictProxy[str]": + """A case-insensitive multidict proxy with all headers.""" + return self._headers + + @reify + def raw_headers(self) -> RawHeaders: + """A sequence of pairs for all headers.""" + return self._message.raw_headers + + @reify + def if_modified_since(self) -> Optional[datetime.datetime]: + """The value of If-Modified-Since HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_MODIFIED_SINCE)) + + @reify + def if_unmodified_since(self) -> Optional[datetime.datetime]: + """The value of If-Unmodified-Since HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_UNMODIFIED_SINCE)) + + @staticmethod + def _etag_values(etag_header: str) -> Iterator[ETag]: + """Extract `ETag` objects from raw header.""" + if etag_header == ETAG_ANY: + yield ETag( + is_weak=False, + value=ETAG_ANY, + ) + else: + for match in LIST_QUOTED_ETAG_RE.finditer(etag_header): + is_weak, value, garbage = match.group(2, 3, 4) + # Any symbol captured by 4th group means + # that the following sequence is invalid. + if garbage: + break + + yield ETag( + is_weak=bool(is_weak), + value=value, + ) + + @classmethod + def _if_match_or_none_impl( + cls, header_value: Optional[str] + ) -> Optional[Tuple[ETag, ...]]: + if not header_value: + return None + + return tuple(cls._etag_values(header_value)) + + @reify + def if_match(self) -> Optional[Tuple[ETag, ...]]: + """The value of If-Match HTTP header, or None. + + This header is represented as a `tuple` of `ETag` objects. + """ + return self._if_match_or_none_impl(self.headers.get(hdrs.IF_MATCH)) + + @reify + def if_none_match(self) -> Optional[Tuple[ETag, ...]]: + """The value of If-None-Match HTTP header, or None. + + This header is represented as a `tuple` of `ETag` objects. + """ + return self._if_match_or_none_impl(self.headers.get(hdrs.IF_NONE_MATCH)) + + @reify + def if_range(self) -> Optional[datetime.datetime]: + """The value of If-Range HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_RANGE)) + + @reify + def keep_alive(self) -> bool: + """Is keepalive enabled by client?""" + return not self._message.should_close + + @reify + def cookies(self) -> Mapping[str, str]: + """Return request cookies. + + A read-only dictionary-like object. + """ + raw = self.headers.get(hdrs.COOKIE, "") + parsed: SimpleCookie[str] = SimpleCookie(raw) + return MappingProxyType({key: val.value for key, val in parsed.items()}) + + @reify + def http_range(self) -> slice: + """The content of Range HTTP header. + + Return a slice instance. + + """ + rng = self._headers.get(hdrs.RANGE) + start, end = None, None + if rng is not None: + try: + pattern = r"^bytes=(\d*)-(\d*)$" + start, end = re.findall(pattern, rng)[0] + except IndexError: # pattern was not found in header + raise ValueError("range not in acceptable format") + + end = int(end) if end else None + start = int(start) if start else None + + if start is None and end is not None: + # end with no start is to return tail of content + start = -end + end = None + + if start is not None and end is not None: + # end is inclusive in range header, exclusive for slice + end += 1 + + if start >= end: + raise ValueError("start cannot be after end") + + if start is end is None: # No valid range supplied + raise ValueError("No start or end of range specified") + + return slice(start, end, 1) + + @reify + def content(self) -> StreamReader: + """Return raw payload stream.""" + return self._payload + + @property + def has_body(self) -> bool: + """Return True if request's HTTP BODY can be read, False otherwise.""" + warnings.warn( + "Deprecated, use .can_read_body #2005", DeprecationWarning, stacklevel=2 + ) + return not self._payload.at_eof() + + @property + def can_read_body(self) -> bool: + """Return True if request's HTTP BODY can be read, False otherwise.""" + return not self._payload.at_eof() + + @reify + def body_exists(self) -> bool: + """Return True if request has HTTP BODY, False otherwise.""" + return type(self._payload) is not EmptyStreamReader + + async def release(self) -> None: + """Release request. + + Eat unread part of HTTP BODY if present. + """ + while not self._payload.at_eof(): + await self._payload.readany() + + async def read(self) -> bytes: + """Read request body if present. + + Returns bytes object with full request content. + """ + if self._read_bytes is None: + body = bytearray() + while True: + chunk = await self._payload.readany() + body.extend(chunk) + if self._client_max_size: + body_size = len(body) + if body_size >= self._client_max_size: + raise HTTPRequestEntityTooLarge( + max_size=self._client_max_size, actual_size=body_size + ) + if not chunk: + break + self._read_bytes = bytes(body) + return self._read_bytes + + async def text(self) -> str: + """Return BODY as text using encoding from .charset.""" + bytes_body = await self.read() + encoding = self.charset or "utf-8" + return bytes_body.decode(encoding) + + async def json(self, *, loads: JSONDecoder = DEFAULT_JSON_DECODER) -> Any: + """Return BODY as JSON.""" + body = await self.text() + return loads(body) + + async def multipart(self) -> MultipartReader: + """Return async iterator to process BODY as multipart.""" + return MultipartReader(self._headers, self._payload) + + async def post(self) -> "MultiDictProxy[Union[str, bytes, FileField]]": + """Return POST parameters.""" + if self._post is not None: + return self._post + if self._method not in self.POST_METHODS: + self._post = MultiDictProxy(MultiDict()) + return self._post + + content_type = self.content_type + if content_type not in ( + "", + "application/x-www-form-urlencoded", + "multipart/form-data", + ): + self._post = MultiDictProxy(MultiDict()) + return self._post + + out: MultiDict[Union[str, bytes, FileField]] = MultiDict() + + if content_type == "multipart/form-data": + multipart = await self.multipart() + max_size = self._client_max_size + + field = await multipart.next() + while field is not None: + size = 0 + field_ct = field.headers.get(hdrs.CONTENT_TYPE) + + if isinstance(field, BodyPartReader): + assert field.name is not None + + # Note that according to RFC 7578, the Content-Type header + # is optional, even for files, so we can't assume it's + # present. + # https://tools.ietf.org/html/rfc7578#section-4.4 + if field.filename: + # store file in temp file + tmp = tempfile.TemporaryFile() + chunk = await field.read_chunk(size=2**16) + while chunk: + chunk = field.decode(chunk) + tmp.write(chunk) + size += len(chunk) + if 0 < max_size < size: + tmp.close() + raise HTTPRequestEntityTooLarge( + max_size=max_size, actual_size=size + ) + chunk = await field.read_chunk(size=2**16) + tmp.seek(0) + + if field_ct is None: + field_ct = "application/octet-stream" + + ff = FileField( + field.name, + field.filename, + cast(io.BufferedReader, tmp), + field_ct, + field.headers, + ) + out.add(field.name, ff) + else: + # deal with ordinary data + value = await field.read(decode=True) + if field_ct is None or field_ct.startswith("text/"): + charset = field.get_charset(default="utf-8") + out.add(field.name, value.decode(charset)) + else: + out.add(field.name, value) + size += len(value) + if 0 < max_size < size: + raise HTTPRequestEntityTooLarge( + max_size=max_size, actual_size=size + ) + else: + raise ValueError( + "To decode nested multipart you need " "to use custom reader", + ) + + field = await multipart.next() + else: + data = await self.read() + if data: + charset = self.charset or "utf-8" + out.extend( + parse_qsl( + data.rstrip().decode(charset), + keep_blank_values=True, + encoding=charset, + ) + ) + + self._post = MultiDictProxy(out) + return self._post + + def get_extra_info(self, name: str, default: Any = None) -> Any: + """Extra info from protocol transport""" + protocol = self._protocol + if protocol is None: + return default + + transport = protocol.transport + if transport is None: + return default + + return transport.get_extra_info(name, default) + + def __repr__(self) -> str: + ascii_encodable_path = self.path.encode("ascii", "backslashreplace").decode( + "ascii" + ) + return "<{} {} {} >".format( + self.__class__.__name__, self._method, ascii_encodable_path + ) + + def __eq__(self, other: object) -> bool: + return id(self) == id(other) + + def __bool__(self) -> bool: + return True + + async def _prepare_hook(self, response: StreamResponse) -> None: + return + + def _cancel(self, exc: BaseException) -> None: + self._payload.set_exception(exc) + + +class Request(BaseRequest): + + ATTRS = BaseRequest.ATTRS | frozenset(["_match_info"]) + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + # matchdict, route_name, handler + # or information about traversal lookup + + # initialized after route resolving + self._match_info: Optional[UrlMappingMatchInfo] = None + + if DEBUG: + + def __setattr__(self, name: str, val: Any) -> None: + if name not in self.ATTRS: + warnings.warn( + "Setting custom {}.{} attribute " + "is discouraged".format(self.__class__.__name__, name), + DeprecationWarning, + stacklevel=2, + ) + super().__setattr__(name, val) + + def clone( + self, + *, + method: str = sentinel, + rel_url: StrOrURL = sentinel, + headers: LooseHeaders = sentinel, + scheme: str = sentinel, + host: str = sentinel, + remote: str = sentinel, + ) -> "Request": + ret = super().clone( + method=method, + rel_url=rel_url, + headers=headers, + scheme=scheme, + host=host, + remote=remote, + ) + new_ret = cast(Request, ret) + new_ret._match_info = self._match_info + return new_ret + + @reify + def match_info(self) -> "UrlMappingMatchInfo": + """Result of route resolving.""" + match_info = self._match_info + assert match_info is not None + return match_info + + @property + def app(self) -> "Application": + """Application instance.""" + match_info = self._match_info + assert match_info is not None + return match_info.current_app + + @property + def config_dict(self) -> ChainMapProxy: + match_info = self._match_info + assert match_info is not None + lst = match_info.apps + app = self.app + idx = lst.index(app) + sublist = list(reversed(lst[: idx + 1])) + return ChainMapProxy(sublist) + + async def _prepare_hook(self, response: StreamResponse) -> None: + match_info = self._match_info + if match_info is None: + return + for app in match_info._apps: + await app.on_response_prepare.send(self, response) diff --git a/lib/aiohttp/web_response.py b/lib/aiohttp/web_response.py new file mode 100644 index 0000000..ce07f81 --- /dev/null +++ b/lib/aiohttp/web_response.py @@ -0,0 +1,825 @@ +import asyncio +import collections.abc +import datetime +import enum +import json +import math +import time +import warnings +import zlib +from concurrent.futures import Executor +from http.cookies import Morsel, SimpleCookie +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Iterator, + Mapping, + MutableMapping, + Optional, + Tuple, + Union, + cast, +) + +from multidict import CIMultiDict, istr + +from . import hdrs, payload +from .abc import AbstractStreamWriter +from .helpers import ( + ETAG_ANY, + PY_38, + QUOTED_ETAG_RE, + ETag, + HeadersMixin, + parse_http_date, + rfc822_formatted_time, + sentinel, + validate_etag_value, +) +from .http import RESPONSES, SERVER_SOFTWARE, HttpVersion10, HttpVersion11 +from .payload import Payload +from .typedefs import JSONEncoder, LooseHeaders + +__all__ = ("ContentCoding", "StreamResponse", "Response", "json_response") + + +if TYPE_CHECKING: # pragma: no cover + from .web_request import BaseRequest + + BaseClass = MutableMapping[str, Any] +else: + BaseClass = collections.abc.MutableMapping + + +if not PY_38: + # allow samesite to be used in python < 3.8 + # already permitted in python 3.8, see https://bugs.python.org/issue29613 + Morsel._reserved["samesite"] = "SameSite" # type: ignore[attr-defined] + + +class ContentCoding(enum.Enum): + # The content codings that we have support for. + # + # Additional registered codings are listed at: + # https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding + deflate = "deflate" + gzip = "gzip" + identity = "identity" + + +############################################################ +# HTTP Response classes +############################################################ + + +class StreamResponse(BaseClass, HeadersMixin): + + _length_check = True + + def __init__( + self, + *, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + ) -> None: + self._body = None + self._keep_alive: Optional[bool] = None + self._chunked = False + self._compression = False + self._compression_force: Optional[ContentCoding] = None + self._cookies: SimpleCookie[str] = SimpleCookie() + + self._req: Optional[BaseRequest] = None + self._payload_writer: Optional[AbstractStreamWriter] = None + self._eof_sent = False + self._body_length = 0 + self._state: Dict[str, Any] = {} + + if headers is not None: + self._headers: CIMultiDict[str] = CIMultiDict(headers) + else: + self._headers = CIMultiDict() + + self.set_status(status, reason) + + @property + def prepared(self) -> bool: + return self._payload_writer is not None + + @property + def task(self) -> "Optional[asyncio.Task[None]]": + if self._req: + return self._req.task + else: + return None + + @property + def status(self) -> int: + return self._status + + @property + def chunked(self) -> bool: + return self._chunked + + @property + def compression(self) -> bool: + return self._compression + + @property + def reason(self) -> str: + return self._reason + + def set_status( + self, + status: int, + reason: Optional[str] = None, + _RESPONSES: Mapping[int, Tuple[str, str]] = RESPONSES, + ) -> None: + assert not self.prepared, ( + "Cannot change the response status code after " "the headers have been sent" + ) + self._status = int(status) + if reason is None: + try: + reason = _RESPONSES[self._status][0] + except Exception: + reason = "" + self._reason = reason + + @property + def keep_alive(self) -> Optional[bool]: + return self._keep_alive + + def force_close(self) -> None: + self._keep_alive = False + + @property + def body_length(self) -> int: + return self._body_length + + @property + def output_length(self) -> int: + warnings.warn("output_length is deprecated", DeprecationWarning) + assert self._payload_writer + return self._payload_writer.buffer_size + + def enable_chunked_encoding(self, chunk_size: Optional[int] = None) -> None: + """Enables automatic chunked transfer encoding.""" + self._chunked = True + + if hdrs.CONTENT_LENGTH in self._headers: + raise RuntimeError( + "You can't enable chunked encoding when " "a content length is set" + ) + if chunk_size is not None: + warnings.warn("Chunk size is deprecated #1615", DeprecationWarning) + + def enable_compression( + self, force: Optional[Union[bool, ContentCoding]] = None + ) -> None: + """Enables response compression encoding.""" + # Backwards compatibility for when force was a bool <0.17. + if type(force) == bool: + force = ContentCoding.deflate if force else ContentCoding.identity + warnings.warn( + "Using boolean for force is deprecated #3318", DeprecationWarning + ) + elif force is not None: + assert isinstance(force, ContentCoding), ( + "force should one of " "None, bool or " "ContentEncoding" + ) + + self._compression = True + self._compression_force = force + + @property + def headers(self) -> "CIMultiDict[str]": + return self._headers + + @property + def cookies(self) -> "SimpleCookie[str]": + return self._cookies + + def set_cookie( + self, + name: str, + value: str, + *, + expires: Optional[str] = None, + domain: Optional[str] = None, + max_age: Optional[Union[int, str]] = None, + path: str = "/", + secure: Optional[bool] = None, + httponly: Optional[bool] = None, + version: Optional[str] = None, + samesite: Optional[str] = None, + ) -> None: + """Set or update response cookie. + + Sets new cookie or updates existent with new value. + Also updates only those params which are not None. + """ + old = self._cookies.get(name) + if old is not None and old.coded_value == "": + # deleted cookie + self._cookies.pop(name, None) + + self._cookies[name] = value + c = self._cookies[name] + + if expires is not None: + c["expires"] = expires + elif c.get("expires") == "Thu, 01 Jan 1970 00:00:00 GMT": + del c["expires"] + + if domain is not None: + c["domain"] = domain + + if max_age is not None: + c["max-age"] = str(max_age) + elif "max-age" in c: + del c["max-age"] + + c["path"] = path + + if secure is not None: + c["secure"] = secure + if httponly is not None: + c["httponly"] = httponly + if version is not None: + c["version"] = version + if samesite is not None: + c["samesite"] = samesite + + def del_cookie( + self, name: str, *, domain: Optional[str] = None, path: str = "/" + ) -> None: + """Delete cookie. + + Creates new empty expired cookie. + """ + # TODO: do we need domain/path here? + self._cookies.pop(name, None) + self.set_cookie( + name, + "", + max_age=0, + expires="Thu, 01 Jan 1970 00:00:00 GMT", + domain=domain, + path=path, + ) + + @property + def content_length(self) -> Optional[int]: + # Just a placeholder for adding setter + return super().content_length + + @content_length.setter + def content_length(self, value: Optional[int]) -> None: + if value is not None: + value = int(value) + if self._chunked: + raise RuntimeError( + "You can't set content length when " "chunked encoding is enable" + ) + self._headers[hdrs.CONTENT_LENGTH] = str(value) + else: + self._headers.pop(hdrs.CONTENT_LENGTH, None) + + @property + def content_type(self) -> str: + # Just a placeholder for adding setter + return super().content_type + + @content_type.setter + def content_type(self, value: str) -> None: + self.content_type # read header values if needed + self._content_type = str(value) + self._generate_content_type_header() + + @property + def charset(self) -> Optional[str]: + # Just a placeholder for adding setter + return super().charset + + @charset.setter + def charset(self, value: Optional[str]) -> None: + ctype = self.content_type # read header values if needed + if ctype == "application/octet-stream": + raise RuntimeError( + "Setting charset for application/octet-stream " + "doesn't make sense, setup content_type first" + ) + assert self._content_dict is not None + if value is None: + self._content_dict.pop("charset", None) + else: + self._content_dict["charset"] = str(value).lower() + self._generate_content_type_header() + + @property + def last_modified(self) -> Optional[datetime.datetime]: + """The value of Last-Modified HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self._headers.get(hdrs.LAST_MODIFIED)) + + @last_modified.setter + def last_modified( + self, value: Optional[Union[int, float, datetime.datetime, str]] + ) -> None: + if value is None: + self._headers.pop(hdrs.LAST_MODIFIED, None) + elif isinstance(value, (int, float)): + self._headers[hdrs.LAST_MODIFIED] = time.strftime( + "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value)) + ) + elif isinstance(value, datetime.datetime): + self._headers[hdrs.LAST_MODIFIED] = time.strftime( + "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple() + ) + elif isinstance(value, str): + self._headers[hdrs.LAST_MODIFIED] = value + + @property + def etag(self) -> Optional[ETag]: + quoted_value = self._headers.get(hdrs.ETAG) + if not quoted_value: + return None + elif quoted_value == ETAG_ANY: + return ETag(value=ETAG_ANY) + match = QUOTED_ETAG_RE.fullmatch(quoted_value) + if not match: + return None + is_weak, value = match.group(1, 2) + return ETag( + is_weak=bool(is_weak), + value=value, + ) + + @etag.setter + def etag(self, value: Optional[Union[ETag, str]]) -> None: + if value is None: + self._headers.pop(hdrs.ETAG, None) + elif (isinstance(value, str) and value == ETAG_ANY) or ( + isinstance(value, ETag) and value.value == ETAG_ANY + ): + self._headers[hdrs.ETAG] = ETAG_ANY + elif isinstance(value, str): + validate_etag_value(value) + self._headers[hdrs.ETAG] = f'"{value}"' + elif isinstance(value, ETag) and isinstance(value.value, str): + validate_etag_value(value.value) + hdr_value = f'W/"{value.value}"' if value.is_weak else f'"{value.value}"' + self._headers[hdrs.ETAG] = hdr_value + else: + raise ValueError( + f"Unsupported etag type: {type(value)}. " + f"etag must be str, ETag or None" + ) + + def _generate_content_type_header( + self, CONTENT_TYPE: istr = hdrs.CONTENT_TYPE + ) -> None: + assert self._content_dict is not None + assert self._content_type is not None + params = "; ".join(f"{k}={v}" for k, v in self._content_dict.items()) + if params: + ctype = self._content_type + "; " + params + else: + ctype = self._content_type + self._headers[CONTENT_TYPE] = ctype + + async def _do_start_compression(self, coding: ContentCoding) -> None: + if coding != ContentCoding.identity: + assert self._payload_writer is not None + self._headers[hdrs.CONTENT_ENCODING] = coding.value + self._payload_writer.enable_compression(coding.value) + # Compressed payload may have different content length, + # remove the header + self._headers.popall(hdrs.CONTENT_LENGTH, None) + + async def _start_compression(self, request: "BaseRequest") -> None: + if self._compression_force: + await self._do_start_compression(self._compression_force) + else: + accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower() + for coding in ContentCoding: + if coding.value in accept_encoding: + await self._do_start_compression(coding) + return + + async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]: + if self._eof_sent: + return None + if self._payload_writer is not None: + return self._payload_writer + + return await self._start(request) + + async def _start(self, request: "BaseRequest") -> AbstractStreamWriter: + self._req = request + writer = self._payload_writer = request._payload_writer + + await self._prepare_headers() + await request._prepare_hook(self) + await self._write_headers() + + return writer + + async def _prepare_headers(self) -> None: + request = self._req + assert request is not None + writer = self._payload_writer + assert writer is not None + keep_alive = self._keep_alive + if keep_alive is None: + keep_alive = request.keep_alive + self._keep_alive = keep_alive + + version = request.version + + headers = self._headers + for cookie in self._cookies.values(): + value = cookie.output(header="")[1:] + headers.add(hdrs.SET_COOKIE, value) + + if self._compression: + await self._start_compression(request) + + if self._chunked: + if version != HttpVersion11: + raise RuntimeError( + "Using chunked encoding is forbidden " + "for HTTP/{0.major}.{0.minor}".format(request.version) + ) + writer.enable_chunking() + headers[hdrs.TRANSFER_ENCODING] = "chunked" + if hdrs.CONTENT_LENGTH in headers: + del headers[hdrs.CONTENT_LENGTH] + elif self._length_check: + writer.length = self.content_length + if writer.length is None: + if version >= HttpVersion11 and self.status != 204: + writer.enable_chunking() + headers[hdrs.TRANSFER_ENCODING] = "chunked" + if hdrs.CONTENT_LENGTH in headers: + del headers[hdrs.CONTENT_LENGTH] + else: + keep_alive = False + # HTTP 1.1: https://tools.ietf.org/html/rfc7230#section-3.3.2 + # HTTP 1.0: https://tools.ietf.org/html/rfc1945#section-10.4 + elif version >= HttpVersion11 and self.status in (100, 101, 102, 103, 204): + del headers[hdrs.CONTENT_LENGTH] + + if self.status not in (204, 304): + headers.setdefault(hdrs.CONTENT_TYPE, "application/octet-stream") + headers.setdefault(hdrs.DATE, rfc822_formatted_time()) + headers.setdefault(hdrs.SERVER, SERVER_SOFTWARE) + + # connection header + if hdrs.CONNECTION not in headers: + if keep_alive: + if version == HttpVersion10: + headers[hdrs.CONNECTION] = "keep-alive" + else: + if version == HttpVersion11: + headers[hdrs.CONNECTION] = "close" + + async def _write_headers(self) -> None: + request = self._req + assert request is not None + writer = self._payload_writer + assert writer is not None + # status line + version = request.version + status_line = "HTTP/{}.{} {} {}".format( + version[0], version[1], self._status, self._reason + ) + await writer.write_headers(status_line, self._headers) + + async def write(self, data: bytes) -> None: + assert isinstance( + data, (bytes, bytearray, memoryview) + ), "data argument must be byte-ish (%r)" % type(data) + + if self._eof_sent: + raise RuntimeError("Cannot call write() after write_eof()") + if self._payload_writer is None: + raise RuntimeError("Cannot call write() before prepare()") + + await self._payload_writer.write(data) + + async def drain(self) -> None: + assert not self._eof_sent, "EOF has already been sent" + assert self._payload_writer is not None, "Response has not been started" + warnings.warn( + "drain method is deprecated, use await resp.write()", + DeprecationWarning, + stacklevel=2, + ) + await self._payload_writer.drain() + + async def write_eof(self, data: bytes = b"") -> None: + assert isinstance( + data, (bytes, bytearray, memoryview) + ), "data argument must be byte-ish (%r)" % type(data) + + if self._eof_sent: + return + + assert self._payload_writer is not None, "Response has not been started" + + await self._payload_writer.write_eof(data) + self._eof_sent = True + self._req = None + self._body_length = self._payload_writer.output_size + self._payload_writer = None + + def __repr__(self) -> str: + if self._eof_sent: + info = "eof" + elif self.prepared: + assert self._req is not None + info = f"{self._req.method} {self._req.path} " + else: + info = "not prepared" + return f"<{self.__class__.__name__} {self.reason} {info}>" + + def __getitem__(self, key: str) -> Any: + return self._state[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._state[key] = value + + def __delitem__(self, key: str) -> None: + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[str]: + return iter(self._state) + + def __hash__(self) -> int: + return hash(id(self)) + + def __eq__(self, other: object) -> bool: + return self is other + + +class Response(StreamResponse): + def __init__( + self, + *, + body: Any = None, + status: int = 200, + reason: Optional[str] = None, + text: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + content_type: Optional[str] = None, + charset: Optional[str] = None, + zlib_executor_size: Optional[int] = None, + zlib_executor: Optional[Executor] = None, + ) -> None: + if body is not None and text is not None: + raise ValueError("body and text are not allowed together") + + if headers is None: + real_headers: CIMultiDict[str] = CIMultiDict() + elif not isinstance(headers, CIMultiDict): + real_headers = CIMultiDict(headers) + else: + real_headers = headers # = cast('CIMultiDict[str]', headers) + + if content_type is not None and "charset" in content_type: + raise ValueError("charset must not be in content_type " "argument") + + if text is not None: + if hdrs.CONTENT_TYPE in real_headers: + if content_type or charset: + raise ValueError( + "passing both Content-Type header and " + "content_type or charset params " + "is forbidden" + ) + else: + # fast path for filling headers + if not isinstance(text, str): + raise TypeError("text argument must be str (%r)" % type(text)) + if content_type is None: + content_type = "text/plain" + if charset is None: + charset = "utf-8" + real_headers[hdrs.CONTENT_TYPE] = content_type + "; charset=" + charset + body = text.encode(charset) + text = None + else: + if hdrs.CONTENT_TYPE in real_headers: + if content_type is not None or charset is not None: + raise ValueError( + "passing both Content-Type header and " + "content_type or charset params " + "is forbidden" + ) + else: + if content_type is not None: + if charset is not None: + content_type += "; charset=" + charset + real_headers[hdrs.CONTENT_TYPE] = content_type + + super().__init__(status=status, reason=reason, headers=real_headers) + + if text is not None: + self.text = text + else: + self.body = body + + self._compressed_body: Optional[bytes] = None + self._zlib_executor_size = zlib_executor_size + self._zlib_executor = zlib_executor + + @property + def body(self) -> Optional[Union[bytes, Payload]]: + return self._body + + @body.setter + def body( + self, + body: bytes, + CONTENT_TYPE: istr = hdrs.CONTENT_TYPE, + CONTENT_LENGTH: istr = hdrs.CONTENT_LENGTH, + ) -> None: + if body is None: + self._body: Optional[bytes] = None + self._body_payload: bool = False + elif isinstance(body, (bytes, bytearray)): + self._body = body + self._body_payload = False + else: + try: + self._body = body = payload.PAYLOAD_REGISTRY.get(body) + except payload.LookupError: + raise ValueError("Unsupported body type %r" % type(body)) + + self._body_payload = True + + headers = self._headers + + # set content-length header if needed + if not self._chunked and CONTENT_LENGTH not in headers: + size = body.size + if size is not None: + headers[CONTENT_LENGTH] = str(size) + + # set content-type + if CONTENT_TYPE not in headers: + headers[CONTENT_TYPE] = body.content_type + + # copy payload headers + if body.headers: + for (key, value) in body.headers.items(): + if key not in headers: + headers[key] = value + + self._compressed_body = None + + @property + def text(self) -> Optional[str]: + if self._body is None: + return None + return self._body.decode(self.charset or "utf-8") + + @text.setter + def text(self, text: str) -> None: + assert text is None or isinstance( + text, str + ), "text argument must be str (%r)" % type(text) + + if self.content_type == "application/octet-stream": + self.content_type = "text/plain" + if self.charset is None: + self.charset = "utf-8" + + self._body = text.encode(self.charset) + self._body_payload = False + self._compressed_body = None + + @property + def content_length(self) -> Optional[int]: + if self._chunked: + return None + + if hdrs.CONTENT_LENGTH in self._headers: + return super().content_length + + if self._compressed_body is not None: + # Return length of the compressed body + return len(self._compressed_body) + elif self._body_payload: + # A payload without content length, or a compressed payload + return None + elif self._body is not None: + return len(self._body) + else: + return 0 + + @content_length.setter + def content_length(self, value: Optional[int]) -> None: + raise RuntimeError("Content length is set automatically") + + async def write_eof(self, data: bytes = b"") -> None: + if self._eof_sent: + return + if self._compressed_body is None: + body: Optional[Union[bytes, Payload]] = self._body + else: + body = self._compressed_body + assert not data, f"data arg is not supported, got {data!r}" + assert self._req is not None + assert self._payload_writer is not None + if body is not None: + if self._req._method == hdrs.METH_HEAD or self._status in [204, 304]: + await super().write_eof() + elif self._body_payload: + payload = cast(Payload, body) + await payload.write(self._payload_writer) + await super().write_eof() + else: + await super().write_eof(cast(bytes, body)) + else: + await super().write_eof() + + async def _start(self, request: "BaseRequest") -> AbstractStreamWriter: + if not self._chunked and hdrs.CONTENT_LENGTH not in self._headers: + if not self._body_payload: + if self._body is not None: + self._headers[hdrs.CONTENT_LENGTH] = str(len(self._body)) + else: + self._headers[hdrs.CONTENT_LENGTH] = "0" + + return await super()._start(request) + + def _compress_body(self, zlib_mode: int) -> None: + assert zlib_mode > 0 + compressobj = zlib.compressobj(wbits=zlib_mode) + body_in = self._body + assert body_in is not None + self._compressed_body = compressobj.compress(body_in) + compressobj.flush() + + async def _do_start_compression(self, coding: ContentCoding) -> None: + if self._body_payload or self._chunked: + return await super()._do_start_compression(coding) + + if coding != ContentCoding.identity: + # Instead of using _payload_writer.enable_compression, + # compress the whole body + zlib_mode = ( + 16 + zlib.MAX_WBITS if coding == ContentCoding.gzip else zlib.MAX_WBITS + ) + body_in = self._body + assert body_in is not None + if ( + self._zlib_executor_size is not None + and len(body_in) > self._zlib_executor_size + ): + await asyncio.get_event_loop().run_in_executor( + self._zlib_executor, self._compress_body, zlib_mode + ) + else: + self._compress_body(zlib_mode) + + body_out = self._compressed_body + assert body_out is not None + + self._headers[hdrs.CONTENT_ENCODING] = coding.value + self._headers[hdrs.CONTENT_LENGTH] = str(len(body_out)) + + +def json_response( + data: Any = sentinel, + *, + text: Optional[str] = None, + body: Optional[bytes] = None, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + content_type: str = "application/json", + dumps: JSONEncoder = json.dumps, +) -> Response: + if data is not sentinel: + if text or body: + raise ValueError("only one of data, text, or body should be specified") + else: + text = dumps(data) + return Response( + text=text, + body=body, + status=status, + reason=reason, + headers=headers, + content_type=content_type, + ) diff --git a/lib/aiohttp/web_routedef.py b/lib/aiohttp/web_routedef.py new file mode 100644 index 0000000..a1eb0a7 --- /dev/null +++ b/lib/aiohttp/web_routedef.py @@ -0,0 +1,216 @@ +import abc +import os # noqa +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterator, + List, + Optional, + Sequence, + Type, + Union, + overload, +) + +import attr + +from . import hdrs +from .abc import AbstractView +from .typedefs import Handler, PathLike + +if TYPE_CHECKING: # pragma: no cover + from .web_request import Request + from .web_response import StreamResponse + from .web_urldispatcher import AbstractRoute, UrlDispatcher +else: + Request = StreamResponse = UrlDispatcher = AbstractRoute = None + + +__all__ = ( + "AbstractRouteDef", + "RouteDef", + "StaticDef", + "RouteTableDef", + "head", + "options", + "get", + "post", + "patch", + "put", + "delete", + "route", + "view", + "static", +) + + +class AbstractRouteDef(abc.ABC): + @abc.abstractmethod + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + pass # pragma: no cover + + +_HandlerType = Union[Type[AbstractView], Handler] + + +@attr.s(auto_attribs=True, frozen=True, repr=False, slots=True) +class RouteDef(AbstractRouteDef): + method: str + path: str + handler: _HandlerType + kwargs: Dict[str, Any] + + def __repr__(self) -> str: + info = [] + for name, value in sorted(self.kwargs.items()): + info.append(f", {name}={value!r}") + return " {handler.__name__!r}" "{info}>".format( + method=self.method, path=self.path, handler=self.handler, info="".join(info) + ) + + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + if self.method in hdrs.METH_ALL: + reg = getattr(router, "add_" + self.method.lower()) + return [reg(self.path, self.handler, **self.kwargs)] + else: + return [ + router.add_route(self.method, self.path, self.handler, **self.kwargs) + ] + + +@attr.s(auto_attribs=True, frozen=True, repr=False, slots=True) +class StaticDef(AbstractRouteDef): + prefix: str + path: PathLike + kwargs: Dict[str, Any] + + def __repr__(self) -> str: + info = [] + for name, value in sorted(self.kwargs.items()): + info.append(f", {name}={value!r}") + return " {path}" "{info}>".format( + prefix=self.prefix, path=self.path, info="".join(info) + ) + + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + resource = router.add_static(self.prefix, self.path, **self.kwargs) + routes = resource.get_info().get("routes", {}) + return list(routes.values()) + + +def route(method: str, path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return RouteDef(method, path, handler, kwargs) + + +def head(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_HEAD, path, handler, **kwargs) + + +def options(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_OPTIONS, path, handler, **kwargs) + + +def get( + path: str, + handler: _HandlerType, + *, + name: Optional[str] = None, + allow_head: bool = True, + **kwargs: Any, +) -> RouteDef: + return route( + hdrs.METH_GET, path, handler, name=name, allow_head=allow_head, **kwargs + ) + + +def post(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_POST, path, handler, **kwargs) + + +def put(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_PUT, path, handler, **kwargs) + + +def patch(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_PATCH, path, handler, **kwargs) + + +def delete(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_DELETE, path, handler, **kwargs) + + +def view(path: str, handler: Type[AbstractView], **kwargs: Any) -> RouteDef: + return route(hdrs.METH_ANY, path, handler, **kwargs) + + +def static(prefix: str, path: PathLike, **kwargs: Any) -> StaticDef: + return StaticDef(prefix, path, kwargs) + + +_Deco = Callable[[_HandlerType], _HandlerType] + + +class RouteTableDef(Sequence[AbstractRouteDef]): + """Route definition table""" + + def __init__(self) -> None: + self._items: List[AbstractRouteDef] = [] + + def __repr__(self) -> str: + return f"" + + @overload + def __getitem__(self, index: int) -> AbstractRouteDef: + ... + + @overload + def __getitem__(self, index: slice) -> List[AbstractRouteDef]: + ... + + def __getitem__(self, index): # type: ignore[no-untyped-def] + return self._items[index] + + def __iter__(self) -> Iterator[AbstractRouteDef]: + return iter(self._items) + + def __len__(self) -> int: + return len(self._items) + + def __contains__(self, item: object) -> bool: + return item in self._items + + def route(self, method: str, path: str, **kwargs: Any) -> _Deco: + def inner(handler: _HandlerType) -> _HandlerType: + self._items.append(RouteDef(method, path, handler, kwargs)) + return handler + + return inner + + def head(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_HEAD, path, **kwargs) + + def get(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_GET, path, **kwargs) + + def post(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_POST, path, **kwargs) + + def put(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_PUT, path, **kwargs) + + def patch(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_PATCH, path, **kwargs) + + def delete(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_DELETE, path, **kwargs) + + def options(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_OPTIONS, path, **kwargs) + + def view(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_ANY, path, **kwargs) + + def static(self, prefix: str, path: PathLike, **kwargs: Any) -> None: + self._items.append(StaticDef(prefix, path, kwargs)) diff --git a/lib/aiohttp/web_runner.py b/lib/aiohttp/web_runner.py new file mode 100644 index 0000000..9282bb9 --- /dev/null +++ b/lib/aiohttp/web_runner.py @@ -0,0 +1,381 @@ +import asyncio +import signal +import socket +from abc import ABC, abstractmethod +from typing import Any, List, Optional, Set + +from yarl import URL + +from .web_app import Application +from .web_server import Server + +try: + from ssl import SSLContext +except ImportError: + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ( + "BaseSite", + "TCPSite", + "UnixSite", + "NamedPipeSite", + "SockSite", + "BaseRunner", + "AppRunner", + "ServerRunner", + "GracefulExit", +) + + +class GracefulExit(SystemExit): + code = 1 + + +def _raise_graceful_exit() -> None: + raise GracefulExit() + + +class BaseSite(ABC): + __slots__ = ("_runner", "_shutdown_timeout", "_ssl_context", "_backlog", "_server") + + def __init__( + self, + runner: "BaseRunner", + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + if runner.server is None: + raise RuntimeError("Call runner.setup() before making a site") + self._runner = runner + self._shutdown_timeout = shutdown_timeout + self._ssl_context = ssl_context + self._backlog = backlog + self._server: Optional[asyncio.AbstractServer] = None + + @property + @abstractmethod + def name(self) -> str: + pass # pragma: no cover + + @abstractmethod + async def start(self) -> None: + self._runner._reg_site(self) + + async def stop(self) -> None: + self._runner._check_site(self) + if self._server is None: + self._runner._unreg_site(self) + return # not started yet + self._server.close() + # named pipes do not have wait_closed property + if hasattr(self._server, "wait_closed"): + await self._server.wait_closed() + await self._runner.shutdown() + assert self._runner.server + await self._runner.server.shutdown(self._shutdown_timeout) + self._runner._unreg_site(self) + + +class TCPSite(BaseSite): + __slots__ = ("_host", "_port", "_reuse_address", "_reuse_port") + + def __init__( + self, + runner: "BaseRunner", + host: Optional[str] = None, + port: Optional[int] = None, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._host = host + if port is None: + port = 8443 if self._ssl_context else 8080 + self._port = port + self._reuse_address = reuse_address + self._reuse_port = reuse_port + + @property + def name(self) -> str: + scheme = "https" if self._ssl_context else "http" + host = "0.0.0.0" if self._host is None else self._host + return str(URL.build(scheme=scheme, host=host, port=self._port)) + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_server( + server, + self._host, + self._port, + ssl=self._ssl_context, + backlog=self._backlog, + reuse_address=self._reuse_address, + reuse_port=self._reuse_port, + ) + + +class UnixSite(BaseSite): + __slots__ = ("_path",) + + def __init__( + self, + runner: "BaseRunner", + path: str, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._path = path + + @property + def name(self) -> str: + scheme = "https" if self._ssl_context else "http" + return f"{scheme}://unix:{self._path}:" + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_unix_server( + server, self._path, ssl=self._ssl_context, backlog=self._backlog + ) + + +class NamedPipeSite(BaseSite): + __slots__ = ("_path",) + + def __init__( + self, runner: "BaseRunner", path: str, *, shutdown_timeout: float = 60.0 + ) -> None: + loop = asyncio.get_event_loop() + if not isinstance( + loop, asyncio.ProactorEventLoop # type: ignore[attr-defined] + ): + raise RuntimeError( + "Named Pipes only available in proactor" "loop under windows" + ) + super().__init__(runner, shutdown_timeout=shutdown_timeout) + self._path = path + + @property + def name(self) -> str: + return self._path + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + _server = await loop.start_serving_pipe( # type: ignore[attr-defined] + server, self._path + ) + self._server = _server[0] + + +class SockSite(BaseSite): + __slots__ = ("_sock", "_name") + + def __init__( + self, + runner: "BaseRunner", + sock: socket.socket, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._sock = sock + scheme = "https" if self._ssl_context else "http" + if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: + name = f"{scheme}://unix:{sock.getsockname()}:" + else: + host, port = sock.getsockname()[:2] + name = str(URL.build(scheme=scheme, host=host, port=port)) + self._name = name + + @property + def name(self) -> str: + return self._name + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_server( + server, sock=self._sock, ssl=self._ssl_context, backlog=self._backlog + ) + + +class BaseRunner(ABC): + __slots__ = ("_handle_signals", "_kwargs", "_server", "_sites") + + def __init__(self, *, handle_signals: bool = False, **kwargs: Any) -> None: + self._handle_signals = handle_signals + self._kwargs = kwargs + self._server: Optional[Server] = None + self._sites: List[BaseSite] = [] + + @property + def server(self) -> Optional[Server]: + return self._server + + @property + def addresses(self) -> List[Any]: + ret: List[Any] = [] + for site in self._sites: + server = site._server + if server is not None: + sockets = server.sockets + if sockets is not None: + for sock in sockets: + ret.append(sock.getsockname()) + return ret + + @property + def sites(self) -> Set[BaseSite]: + return set(self._sites) + + async def setup(self) -> None: + loop = asyncio.get_event_loop() + + if self._handle_signals: + try: + loop.add_signal_handler(signal.SIGINT, _raise_graceful_exit) + loop.add_signal_handler(signal.SIGTERM, _raise_graceful_exit) + except NotImplementedError: # pragma: no cover + # add_signal_handler is not implemented on Windows + pass + + self._server = await self._make_server() + + @abstractmethod + async def shutdown(self) -> None: + pass # pragma: no cover + + async def cleanup(self) -> None: + loop = asyncio.get_event_loop() + + # The loop over sites is intentional, an exception on gather() + # leaves self._sites in unpredictable state. + # The loop guaranties that a site is either deleted on success or + # still present on failure + for site in list(self._sites): + await site.stop() + await self._cleanup_server() + self._server = None + if self._handle_signals: + try: + loop.remove_signal_handler(signal.SIGINT) + loop.remove_signal_handler(signal.SIGTERM) + except NotImplementedError: # pragma: no cover + # remove_signal_handler is not implemented on Windows + pass + + @abstractmethod + async def _make_server(self) -> Server: + pass # pragma: no cover + + @abstractmethod + async def _cleanup_server(self) -> None: + pass # pragma: no cover + + def _reg_site(self, site: BaseSite) -> None: + if site in self._sites: + raise RuntimeError(f"Site {site} is already registered in runner {self}") + self._sites.append(site) + + def _check_site(self, site: BaseSite) -> None: + if site not in self._sites: + raise RuntimeError(f"Site {site} is not registered in runner {self}") + + def _unreg_site(self, site: BaseSite) -> None: + if site not in self._sites: + raise RuntimeError(f"Site {site} is not registered in runner {self}") + self._sites.remove(site) + + +class ServerRunner(BaseRunner): + """Low-level web server runner""" + + __slots__ = ("_web_server",) + + def __init__( + self, web_server: Server, *, handle_signals: bool = False, **kwargs: Any + ) -> None: + super().__init__(handle_signals=handle_signals, **kwargs) + self._web_server = web_server + + async def shutdown(self) -> None: + pass + + async def _make_server(self) -> Server: + return self._web_server + + async def _cleanup_server(self) -> None: + pass + + +class AppRunner(BaseRunner): + """Web Application runner""" + + __slots__ = ("_app",) + + def __init__( + self, app: Application, *, handle_signals: bool = False, **kwargs: Any + ) -> None: + super().__init__(handle_signals=handle_signals, **kwargs) + if not isinstance(app, Application): + raise TypeError( + "The first argument should be web.Application " + "instance, got {!r}".format(app) + ) + self._app = app + + @property + def app(self) -> Application: + return self._app + + async def shutdown(self) -> None: + await self._app.shutdown() + + async def _make_server(self) -> Server: + loop = asyncio.get_event_loop() + self._app._set_loop(loop) + self._app.on_startup.freeze() + await self._app.startup() + self._app.freeze() + + return self._app._make_handler(loop=loop, **self._kwargs) + + async def _cleanup_server(self) -> None: + await self._app.cleanup() diff --git a/lib/aiohttp/web_server.py b/lib/aiohttp/web_server.py new file mode 100644 index 0000000..fa46e90 --- /dev/null +++ b/lib/aiohttp/web_server.py @@ -0,0 +1,62 @@ +"""Low level HTTP server.""" +import asyncio +from typing import Any, Awaitable, Callable, Dict, List, Optional # noqa + +from .abc import AbstractStreamWriter +from .helpers import get_running_loop +from .http_parser import RawRequestMessage +from .streams import StreamReader +from .web_protocol import RequestHandler, _RequestFactory, _RequestHandler +from .web_request import BaseRequest + +__all__ = ("Server",) + + +class Server: + def __init__( + self, + handler: _RequestHandler, + *, + request_factory: Optional[_RequestFactory] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + **kwargs: Any + ) -> None: + self._loop = get_running_loop(loop) + self._connections: Dict[RequestHandler, asyncio.Transport] = {} + self._kwargs = kwargs + self.requests_count = 0 + self.request_handler = handler + self.request_factory = request_factory or self._make_request + + @property + def connections(self) -> List[RequestHandler]: + return list(self._connections.keys()) + + def connection_made( + self, handler: RequestHandler, transport: asyncio.Transport + ) -> None: + self._connections[handler] = transport + + def connection_lost( + self, handler: RequestHandler, exc: Optional[BaseException] = None + ) -> None: + if handler in self._connections: + del self._connections[handler] + + def _make_request( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: RequestHandler, + writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + ) -> BaseRequest: + return BaseRequest(message, payload, protocol, writer, task, self._loop) + + async def shutdown(self, timeout: Optional[float] = None) -> None: + coros = [conn.shutdown(timeout) for conn in self._connections] + await asyncio.gather(*coros) + self._connections.clear() + + def __call__(self) -> RequestHandler: + return RequestHandler(self, loop=self._loop, **self._kwargs) diff --git a/lib/aiohttp/web_urldispatcher.py b/lib/aiohttp/web_urldispatcher.py new file mode 100644 index 0000000..5942e35 --- /dev/null +++ b/lib/aiohttp/web_urldispatcher.py @@ -0,0 +1,1220 @@ +import abc +import asyncio +import base64 +import hashlib +import inspect +import keyword +import os +import re +import warnings +from contextlib import contextmanager +from functools import wraps +from pathlib import Path +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Container, + Dict, + Generator, + Iterable, + Iterator, + List, + Mapping, + Optional, + Pattern, + Set, + Sized, + Tuple, + Type, + Union, + cast, +) + +from yarl import URL, __version__ as yarl_version # type: ignore[attr-defined] + +from . import hdrs +from .abc import AbstractMatchInfo, AbstractRouter, AbstractView +from .helpers import DEBUG +from .http import HttpVersion11 +from .typedefs import Final, Handler, PathLike, TypedDict +from .web_exceptions import ( + HTTPException, + HTTPExpectationFailed, + HTTPForbidden, + HTTPMethodNotAllowed, + HTTPNotFound, +) +from .web_fileresponse import FileResponse +from .web_request import Request +from .web_response import Response, StreamResponse +from .web_routedef import AbstractRouteDef + +__all__ = ( + "UrlDispatcher", + "UrlMappingMatchInfo", + "AbstractResource", + "Resource", + "PlainResource", + "DynamicResource", + "AbstractRoute", + "ResourceRoute", + "StaticResource", + "View", +) + + +if TYPE_CHECKING: # pragma: no cover + from .web_app import Application + + BaseDict = Dict[str, str] +else: + BaseDict = dict + +YARL_VERSION: Final[Tuple[int, ...]] = tuple(map(int, yarl_version.split(".")[:2])) + +HTTP_METHOD_RE: Final[Pattern[str]] = re.compile( + r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$" +) +ROUTE_RE: Final[Pattern[str]] = re.compile( + r"(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})" +) +PATH_SEP: Final[str] = re.escape("/") + + +_ExpectHandler = Callable[[Request], Awaitable[None]] +_Resolve = Tuple[Optional["UrlMappingMatchInfo"], Set[str]] + + +class _InfoDict(TypedDict, total=False): + path: str + + formatter: str + pattern: Pattern[str] + + directory: Path + prefix: str + routes: Mapping[str, "AbstractRoute"] + + app: "Application" + + domain: str + + rule: "AbstractRuleMatching" + + http_exception: HTTPException + + +class AbstractResource(Sized, Iterable["AbstractRoute"]): + def __init__(self, *, name: Optional[str] = None) -> None: + self._name = name + + @property + def name(self) -> Optional[str]: + return self._name + + @property + @abc.abstractmethod + def canonical(self) -> str: + """Exposes the resource's canonical path. + + For example '/foo/bar/{name}' + + """ + + @abc.abstractmethod # pragma: no branch + def url_for(self, **kwargs: str) -> URL: + """Construct url for resource with additional params.""" + + @abc.abstractmethod # pragma: no branch + async def resolve(self, request: Request) -> _Resolve: + """Resolve resource. + + Return (UrlMappingMatchInfo, allowed_methods) pair. + """ + + @abc.abstractmethod + def add_prefix(self, prefix: str) -> None: + """Add a prefix to processed URLs. + + Required for subapplications support. + """ + + @abc.abstractmethod + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + def freeze(self) -> None: + pass + + @abc.abstractmethod + def raw_match(self, path: str) -> bool: + """Perform a raw match against path""" + + +class AbstractRoute(abc.ABC): + def __init__( + self, + method: str, + handler: Union[Handler, Type[AbstractView]], + *, + expect_handler: Optional[_ExpectHandler] = None, + resource: Optional[AbstractResource] = None, + ) -> None: + + if expect_handler is None: + expect_handler = _default_expect_handler + + assert asyncio.iscoroutinefunction( + expect_handler + ), f"Coroutine is expected, got {expect_handler!r}" + + method = method.upper() + if not HTTP_METHOD_RE.match(method): + raise ValueError(f"{method} is not allowed HTTP method") + + assert callable(handler), handler + if asyncio.iscoroutinefunction(handler): + pass + elif inspect.isgeneratorfunction(handler): + warnings.warn( + "Bare generators are deprecated, " "use @coroutine wrapper", + DeprecationWarning, + ) + elif isinstance(handler, type) and issubclass(handler, AbstractView): + pass + else: + warnings.warn( + "Bare functions are deprecated, " "use async ones", DeprecationWarning + ) + + @wraps(handler) + async def handler_wrapper(request: Request) -> StreamResponse: + result = old_handler(request) + if asyncio.iscoroutine(result): + return await result + return result # type: ignore[return-value] + + old_handler = handler + handler = handler_wrapper + + self._method = method + self._handler = handler + self._expect_handler = expect_handler + self._resource = resource + + @property + def method(self) -> str: + return self._method + + @property + def handler(self) -> Handler: + return self._handler + + @property + @abc.abstractmethod + def name(self) -> Optional[str]: + """Optional route's name, always equals to resource's name.""" + + @property + def resource(self) -> Optional[AbstractResource]: + return self._resource + + @abc.abstractmethod + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + @abc.abstractmethod # pragma: no branch + def url_for(self, *args: str, **kwargs: str) -> URL: + """Construct url for route with additional params.""" + + async def handle_expect_header(self, request: Request) -> None: + await self._expect_handler(request) + + +class UrlMappingMatchInfo(BaseDict, AbstractMatchInfo): + def __init__(self, match_dict: Dict[str, str], route: AbstractRoute): + super().__init__(match_dict) + self._route = route + self._apps: List[Application] = [] + self._current_app: Optional[Application] = None + self._frozen = False + + @property + def handler(self) -> Handler: + return self._route.handler + + @property + def route(self) -> AbstractRoute: + return self._route + + @property + def expect_handler(self) -> _ExpectHandler: + return self._route.handle_expect_header + + @property + def http_exception(self) -> Optional[HTTPException]: + return None + + def get_info(self) -> _InfoDict: # type: ignore[override] + return self._route.get_info() + + @property + def apps(self) -> Tuple["Application", ...]: + return tuple(self._apps) + + def add_app(self, app: "Application") -> None: + if self._frozen: + raise RuntimeError("Cannot change apps stack after .freeze() call") + if self._current_app is None: + self._current_app = app + self._apps.insert(0, app) + + @property + def current_app(self) -> "Application": + app = self._current_app + assert app is not None + return app + + @contextmanager + def set_current_app(self, app: "Application") -> Generator[None, None, None]: + if DEBUG: # pragma: no cover + if app not in self._apps: + raise RuntimeError( + "Expected one of the following apps {!r}, got {!r}".format( + self._apps, app + ) + ) + prev = self._current_app + self._current_app = app + try: + yield + finally: + self._current_app = prev + + def freeze(self) -> None: + self._frozen = True + + def __repr__(self) -> str: + return f"" + + +class MatchInfoError(UrlMappingMatchInfo): + def __init__(self, http_exception: HTTPException) -> None: + self._exception = http_exception + super().__init__({}, SystemRoute(self._exception)) + + @property + def http_exception(self) -> HTTPException: + return self._exception + + def __repr__(self) -> str: + return "".format( + self._exception.status, self._exception.reason + ) + + +async def _default_expect_handler(request: Request) -> None: + """Default handler for Expect header. + + Just send "100 Continue" to client. + raise HTTPExpectationFailed if value of header is not "100-continue" + """ + expect = request.headers.get(hdrs.EXPECT, "") + if request.version == HttpVersion11: + if expect.lower() == "100-continue": + await request.writer.write(b"HTTP/1.1 100 Continue\r\n\r\n") + else: + raise HTTPExpectationFailed(text="Unknown Expect: %s" % expect) + + +class Resource(AbstractResource): + def __init__(self, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + self._routes: List[ResourceRoute] = [] + + def add_route( + self, + method: str, + handler: Union[Type[AbstractView], Handler], + *, + expect_handler: Optional[_ExpectHandler] = None, + ) -> "ResourceRoute": + + for route_obj in self._routes: + if route_obj.method == method or route_obj.method == hdrs.METH_ANY: + raise RuntimeError( + "Added route will never be executed, " + "method {route.method} is already " + "registered".format(route=route_obj) + ) + + route_obj = ResourceRoute(method, handler, self, expect_handler=expect_handler) + self.register_route(route_obj) + return route_obj + + def register_route(self, route: "ResourceRoute") -> None: + assert isinstance( + route, ResourceRoute + ), f"Instance of Route class is required, got {route!r}" + self._routes.append(route) + + async def resolve(self, request: Request) -> _Resolve: + allowed_methods: Set[str] = set() + + match_dict = self._match(request.rel_url.raw_path) + if match_dict is None: + return None, allowed_methods + + for route_obj in self._routes: + route_method = route_obj.method + allowed_methods.add(route_method) + + if route_method == request.method or route_method == hdrs.METH_ANY: + return (UrlMappingMatchInfo(match_dict, route_obj), allowed_methods) + else: + return None, allowed_methods + + @abc.abstractmethod + def _match(self, path: str) -> Optional[Dict[str, str]]: + pass # pragma: no cover + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator[AbstractRoute]: + return iter(self._routes) + + # TODO: implement all abstract methods + + +class PlainResource(Resource): + def __init__(self, path: str, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + assert not path or path.startswith("/") + self._path = path + + @property + def canonical(self) -> str: + return self._path + + def freeze(self) -> None: + if not self._path: + self._path = "/" + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._path = prefix + self._path + + def _match(self, path: str) -> Optional[Dict[str, str]]: + # string comparison is about 10 times faster than regexp matching + if self._path == path: + return {} + else: + return None + + def raw_match(self, path: str) -> bool: + return self._path == path + + def get_info(self) -> _InfoDict: + return {"path": self._path} + + def url_for(self) -> URL: # type: ignore[override] + return URL.build(path=self._path, encoded=True) + + def __repr__(self) -> str: + name = "'" + self.name + "' " if self.name is not None else "" + return f"" + + +class DynamicResource(Resource): + + DYN = re.compile(r"\{(?P[_a-zA-Z][_a-zA-Z0-9]*)\}") + DYN_WITH_RE = re.compile(r"\{(?P[_a-zA-Z][_a-zA-Z0-9]*):(?P.+)\}") + GOOD = r"[^{}/]+" + + def __init__(self, path: str, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + pattern = "" + formatter = "" + for part in ROUTE_RE.split(path): + match = self.DYN.fullmatch(part) + if match: + pattern += "(?P<{}>{})".format(match.group("var"), self.GOOD) + formatter += "{" + match.group("var") + "}" + continue + + match = self.DYN_WITH_RE.fullmatch(part) + if match: + pattern += "(?P<{var}>{re})".format(**match.groupdict()) + formatter += "{" + match.group("var") + "}" + continue + + if "{" in part or "}" in part: + raise ValueError(f"Invalid path '{path}'['{part}']") + + part = _requote_path(part) + formatter += part + pattern += re.escape(part) + + try: + compiled = re.compile(pattern) + except re.error as exc: + raise ValueError(f"Bad pattern '{pattern}': {exc}") from None + assert compiled.pattern.startswith(PATH_SEP) + assert formatter.startswith("/") + self._pattern = compiled + self._formatter = formatter + + @property + def canonical(self) -> str: + return self._formatter + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._pattern = re.compile(re.escape(prefix) + self._pattern.pattern) + self._formatter = prefix + self._formatter + + def _match(self, path: str) -> Optional[Dict[str, str]]: + match = self._pattern.fullmatch(path) + if match is None: + return None + else: + return { + key: _unquote_path(value) for key, value in match.groupdict().items() + } + + def raw_match(self, path: str) -> bool: + return self._formatter == path + + def get_info(self) -> _InfoDict: + return {"formatter": self._formatter, "pattern": self._pattern} + + def url_for(self, **parts: str) -> URL: + url = self._formatter.format_map({k: _quote_path(v) for k, v in parts.items()}) + return URL.build(path=url, encoded=True) + + def __repr__(self) -> str: + name = "'" + self.name + "' " if self.name is not None else "" + return "".format( + name=name, formatter=self._formatter + ) + + +class PrefixResource(AbstractResource): + def __init__(self, prefix: str, *, name: Optional[str] = None) -> None: + assert not prefix or prefix.startswith("/"), prefix + assert prefix in ("", "/") or not prefix.endswith("/"), prefix + super().__init__(name=name) + self._prefix = _requote_path(prefix) + self._prefix2 = self._prefix + "/" + + @property + def canonical(self) -> str: + return self._prefix + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._prefix = prefix + self._prefix + self._prefix2 = self._prefix + "/" + + def raw_match(self, prefix: str) -> bool: + return False + + # TODO: impl missing abstract methods + + +class StaticResource(PrefixResource): + VERSION_KEY = "v" + + def __init__( + self, + prefix: str, + directory: PathLike, + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + chunk_size: int = 256 * 1024, + show_index: bool = False, + follow_symlinks: bool = False, + append_version: bool = False, + ) -> None: + super().__init__(prefix, name=name) + try: + directory = Path(directory) + if str(directory).startswith("~"): + directory = Path(os.path.expanduser(str(directory))) + directory = directory.resolve() + if not directory.is_dir(): + raise ValueError("Not a directory") + except (FileNotFoundError, ValueError) as error: + raise ValueError(f"No directory exists at '{directory}'") from error + self._directory = directory + self._show_index = show_index + self._chunk_size = chunk_size + self._follow_symlinks = follow_symlinks + self._expect_handler = expect_handler + self._append_version = append_version + + self._routes = { + "GET": ResourceRoute( + "GET", self._handle, self, expect_handler=expect_handler + ), + "HEAD": ResourceRoute( + "HEAD", self._handle, self, expect_handler=expect_handler + ), + } + + def url_for( # type: ignore[override] + self, + *, + filename: Union[str, Path], + append_version: Optional[bool] = None, + ) -> URL: + if append_version is None: + append_version = self._append_version + if isinstance(filename, Path): + filename = str(filename) + filename = filename.lstrip("/") + + url = URL.build(path=self._prefix, encoded=True) + # filename is not encoded + if YARL_VERSION < (1, 6): + url = url / filename.replace("%", "%25") + else: + url = url / filename + + if append_version: + try: + filepath = self._directory.joinpath(filename).resolve() + if not self._follow_symlinks: + filepath.relative_to(self._directory) + except (ValueError, FileNotFoundError): + # ValueError for case when path point to symlink + # with follow_symlinks is False + return url # relatively safe + if filepath.is_file(): + # TODO cache file content + # with file watcher for cache invalidation + with filepath.open("rb") as f: + file_bytes = f.read() + h = self._get_file_hash(file_bytes) + url = url.with_query({self.VERSION_KEY: h}) + return url + return url + + @staticmethod + def _get_file_hash(byte_array: bytes) -> str: + m = hashlib.sha256() # todo sha256 can be configurable param + m.update(byte_array) + b64 = base64.urlsafe_b64encode(m.digest()) + return b64.decode("ascii") + + def get_info(self) -> _InfoDict: + return { + "directory": self._directory, + "prefix": self._prefix, + "routes": self._routes, + } + + def set_options_route(self, handler: Handler) -> None: + if "OPTIONS" in self._routes: + raise RuntimeError("OPTIONS route was set already") + self._routes["OPTIONS"] = ResourceRoute( + "OPTIONS", handler, self, expect_handler=self._expect_handler + ) + + async def resolve(self, request: Request) -> _Resolve: + path = request.rel_url.raw_path + method = request.method + allowed_methods = set(self._routes) + if not path.startswith(self._prefix2) and path != self._prefix: + return None, set() + + if method not in allowed_methods: + return None, allowed_methods + + match_dict = {"filename": _unquote_path(path[len(self._prefix) + 1 :])} + return (UrlMappingMatchInfo(match_dict, self._routes[method]), allowed_methods) + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator[AbstractRoute]: + return iter(self._routes.values()) + + async def _handle(self, request: Request) -> StreamResponse: + rel_url = request.match_info["filename"] + try: + filename = Path(rel_url) + if filename.anchor: + # rel_url is an absolute name like + # /static/\\machine_name\c$ or /static/D:\path + # where the static dir is totally different + raise HTTPForbidden() + filepath = self._directory.joinpath(filename).resolve() + if not self._follow_symlinks: + filepath.relative_to(self._directory) + except (ValueError, FileNotFoundError) as error: + # relatively safe + raise HTTPNotFound() from error + except HTTPForbidden: + raise + except Exception as error: + # perm error or other kind! + request.app.logger.exception(error) + raise HTTPNotFound() from error + + # on opening a dir, load its contents if allowed + if filepath.is_dir(): + if self._show_index: + try: + return Response( + text=self._directory_as_html(filepath), content_type="text/html" + ) + except PermissionError: + raise HTTPForbidden() + else: + raise HTTPForbidden() + elif filepath.is_file(): + return FileResponse(filepath, chunk_size=self._chunk_size) + else: + raise HTTPNotFound + + def _directory_as_html(self, filepath: Path) -> str: + # returns directory's index as html + + # sanity check + assert filepath.is_dir() + + relative_path_to_dir = filepath.relative_to(self._directory).as_posix() + index_of = f"Index of /{relative_path_to_dir}" + h1 = f"

{index_of}

" + + index_list = [] + dir_index = filepath.iterdir() + for _file in sorted(dir_index): + # show file url as relative to static path + rel_path = _file.relative_to(self._directory).as_posix() + file_url = self._prefix + "/" + rel_path + + # if file is a directory, add '/' to the end of the name + if _file.is_dir(): + file_name = f"{_file.name}/" + else: + file_name = _file.name + + index_list.append( + '
'.format( + url=file_url, name=file_name + ) + ) + ul = "
    \n{}\n
".format("\n".join(index_list)) + body = f"\n{h1}\n{ul}\n" + + head_str = f"\n{index_of}\n" + html = f"\n{head_str}\n{body}\n" + + return html + + def __repr__(self) -> str: + name = "'" + self.name + "'" if self.name is not None else "" + return " {directory!r}>".format( + name=name, path=self._prefix, directory=self._directory + ) + + +class PrefixedSubAppResource(PrefixResource): + def __init__(self, prefix: str, app: "Application") -> None: + super().__init__(prefix) + self._app = app + for resource in app.router.resources(): + resource.add_prefix(prefix) + + def add_prefix(self, prefix: str) -> None: + super().add_prefix(prefix) + for resource in self._app.router.resources(): + resource.add_prefix(prefix) + + def url_for(self, *args: str, **kwargs: str) -> URL: + raise RuntimeError(".url_for() is not supported " "by sub-application root") + + def get_info(self) -> _InfoDict: + return {"app": self._app, "prefix": self._prefix} + + async def resolve(self, request: Request) -> _Resolve: + if ( + not request.url.raw_path.startswith(self._prefix2) + and request.url.raw_path != self._prefix + ): + return None, set() + match_info = await self._app.router.resolve(request) + match_info.add_app(self._app) + if isinstance(match_info.http_exception, HTTPMethodNotAllowed): + methods = match_info.http_exception.allowed_methods + else: + methods = set() + return match_info, methods + + def __len__(self) -> int: + return len(self._app.router.routes()) + + def __iter__(self) -> Iterator[AbstractRoute]: + return iter(self._app.router.routes()) + + def __repr__(self) -> str: + return " {app!r}>".format( + prefix=self._prefix, app=self._app + ) + + +class AbstractRuleMatching(abc.ABC): + @abc.abstractmethod # pragma: no branch + async def match(self, request: Request) -> bool: + """Return bool if the request satisfies the criteria""" + + @abc.abstractmethod # pragma: no branch + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + @property + @abc.abstractmethod # pragma: no branch + def canonical(self) -> str: + """Return a str""" + + +class Domain(AbstractRuleMatching): + re_part = re.compile(r"(?!-)[a-z\d-]{1,63}(? None: + super().__init__() + self._domain = self.validation(domain) + + @property + def canonical(self) -> str: + return self._domain + + def validation(self, domain: str) -> str: + if not isinstance(domain, str): + raise TypeError("Domain must be str") + domain = domain.rstrip(".").lower() + if not domain: + raise ValueError("Domain cannot be empty") + elif "://" in domain: + raise ValueError("Scheme not supported") + url = URL("http://" + domain) + assert url.raw_host is not None + if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(".")): + raise ValueError("Domain not valid") + if url.port == 80: + return url.raw_host + return f"{url.raw_host}:{url.port}" + + async def match(self, request: Request) -> bool: + host = request.headers.get(hdrs.HOST) + if not host: + return False + return self.match_domain(host) + + def match_domain(self, host: str) -> bool: + return host.lower() == self._domain + + def get_info(self) -> _InfoDict: + return {"domain": self._domain} + + +class MaskDomain(Domain): + re_part = re.compile(r"(?!-)[a-z\d\*-]{1,63}(? None: + super().__init__(domain) + mask = self._domain.replace(".", r"\.").replace("*", ".*") + self._mask = re.compile(mask) + + @property + def canonical(self) -> str: + return self._mask.pattern + + def match_domain(self, host: str) -> bool: + return self._mask.fullmatch(host) is not None + + +class MatchedSubAppResource(PrefixedSubAppResource): + def __init__(self, rule: AbstractRuleMatching, app: "Application") -> None: + AbstractResource.__init__(self) + self._prefix = "" + self._app = app + self._rule = rule + + @property + def canonical(self) -> str: + return self._rule.canonical + + def get_info(self) -> _InfoDict: + return {"app": self._app, "rule": self._rule} + + async def resolve(self, request: Request) -> _Resolve: + if not await self._rule.match(request): + return None, set() + match_info = await self._app.router.resolve(request) + match_info.add_app(self._app) + if isinstance(match_info.http_exception, HTTPMethodNotAllowed): + methods = match_info.http_exception.allowed_methods + else: + methods = set() + return match_info, methods + + def __repr__(self) -> str: + return " {app!r}>" "".format(app=self._app) + + +class ResourceRoute(AbstractRoute): + """A route with resource""" + + def __init__( + self, + method: str, + handler: Union[Handler, Type[AbstractView]], + resource: AbstractResource, + *, + expect_handler: Optional[_ExpectHandler] = None, + ) -> None: + super().__init__( + method, handler, expect_handler=expect_handler, resource=resource + ) + + def __repr__(self) -> str: + return " {handler!r}".format( + method=self.method, resource=self._resource, handler=self.handler + ) + + @property + def name(self) -> Optional[str]: + if self._resource is None: + return None + return self._resource.name + + def url_for(self, *args: str, **kwargs: str) -> URL: + """Construct url for route with additional params.""" + assert self._resource is not None + return self._resource.url_for(*args, **kwargs) + + def get_info(self) -> _InfoDict: + assert self._resource is not None + return self._resource.get_info() + + +class SystemRoute(AbstractRoute): + def __init__(self, http_exception: HTTPException) -> None: + super().__init__(hdrs.METH_ANY, self._handle) + self._http_exception = http_exception + + def url_for(self, *args: str, **kwargs: str) -> URL: + raise RuntimeError(".url_for() is not allowed for SystemRoute") + + @property + def name(self) -> Optional[str]: + return None + + def get_info(self) -> _InfoDict: + return {"http_exception": self._http_exception} + + async def _handle(self, request: Request) -> StreamResponse: + raise self._http_exception + + @property + def status(self) -> int: + return self._http_exception.status + + @property + def reason(self) -> str: + return self._http_exception.reason + + def __repr__(self) -> str: + return "".format(self=self) + + +class View(AbstractView): + async def _iter(self) -> StreamResponse: + if self.request.method not in hdrs.METH_ALL: + self._raise_allowed_methods() + method: Callable[[], Awaitable[StreamResponse]] = getattr( + self, self.request.method.lower(), None + ) + if method is None: + self._raise_allowed_methods() + resp = await method() + return resp + + def __await__(self) -> Generator[Any, None, StreamResponse]: + return self._iter().__await__() + + def _raise_allowed_methods(self) -> None: + allowed_methods = {m for m in hdrs.METH_ALL if hasattr(self, m.lower())} + raise HTTPMethodNotAllowed(self.request.method, allowed_methods) + + +class ResourcesView(Sized, Iterable[AbstractResource], Container[AbstractResource]): + def __init__(self, resources: List[AbstractResource]) -> None: + self._resources = resources + + def __len__(self) -> int: + return len(self._resources) + + def __iter__(self) -> Iterator[AbstractResource]: + yield from self._resources + + def __contains__(self, resource: object) -> bool: + return resource in self._resources + + +class RoutesView(Sized, Iterable[AbstractRoute], Container[AbstractRoute]): + def __init__(self, resources: List[AbstractResource]): + self._routes: List[AbstractRoute] = [] + for resource in resources: + for route in resource: + self._routes.append(route) + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator[AbstractRoute]: + yield from self._routes + + def __contains__(self, route: object) -> bool: + return route in self._routes + + +class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]): + + NAME_SPLIT_RE = re.compile(r"[.:-]") + + def __init__(self) -> None: + super().__init__() + self._resources: List[AbstractResource] = [] + self._named_resources: Dict[str, AbstractResource] = {} + + async def resolve(self, request: Request) -> UrlMappingMatchInfo: + method = request.method + allowed_methods: Set[str] = set() + + for resource in self._resources: + match_dict, allowed = await resource.resolve(request) + if match_dict is not None: + return match_dict + else: + allowed_methods |= allowed + + if allowed_methods: + return MatchInfoError(HTTPMethodNotAllowed(method, allowed_methods)) + else: + return MatchInfoError(HTTPNotFound()) + + def __iter__(self) -> Iterator[str]: + return iter(self._named_resources) + + def __len__(self) -> int: + return len(self._named_resources) + + def __contains__(self, resource: object) -> bool: + return resource in self._named_resources + + def __getitem__(self, name: str) -> AbstractResource: + return self._named_resources[name] + + def resources(self) -> ResourcesView: + return ResourcesView(self._resources) + + def routes(self) -> RoutesView: + return RoutesView(self._resources) + + def named_resources(self) -> Mapping[str, AbstractResource]: + return MappingProxyType(self._named_resources) + + def register_resource(self, resource: AbstractResource) -> None: + assert isinstance( + resource, AbstractResource + ), f"Instance of AbstractResource class is required, got {resource!r}" + if self.frozen: + raise RuntimeError("Cannot register a resource into frozen router.") + + name = resource.name + + if name is not None: + parts = self.NAME_SPLIT_RE.split(name) + for part in parts: + if keyword.iskeyword(part): + raise ValueError( + f"Incorrect route name {name!r}, " + "python keywords cannot be used " + "for route name" + ) + if not part.isidentifier(): + raise ValueError( + "Incorrect route name {!r}, " + "the name should be a sequence of " + "python identifiers separated " + "by dash, dot or column".format(name) + ) + if name in self._named_resources: + raise ValueError( + "Duplicate {!r}, " + "already handled by {!r}".format(name, self._named_resources[name]) + ) + self._named_resources[name] = resource + self._resources.append(resource) + + def add_resource(self, path: str, *, name: Optional[str] = None) -> Resource: + if path and not path.startswith("/"): + raise ValueError("path should be started with / or be empty") + # Reuse last added resource if path and name are the same + if self._resources: + resource = self._resources[-1] + if resource.name == name and resource.raw_match(path): + return cast(Resource, resource) + if not ("{" in path or "}" in path or ROUTE_RE.search(path)): + resource = PlainResource(_requote_path(path), name=name) + self.register_resource(resource) + return resource + resource = DynamicResource(path, name=name) + self.register_resource(resource) + return resource + + def add_route( + self, + method: str, + path: str, + handler: Union[Handler, Type[AbstractView]], + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + ) -> AbstractRoute: + resource = self.add_resource(path, name=name) + return resource.add_route(method, handler, expect_handler=expect_handler) + + def add_static( + self, + prefix: str, + path: PathLike, + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + chunk_size: int = 256 * 1024, + show_index: bool = False, + follow_symlinks: bool = False, + append_version: bool = False, + ) -> AbstractResource: + """Add static files view. + + prefix - url prefix + path - folder with files + + """ + assert prefix.startswith("/") + if prefix.endswith("/"): + prefix = prefix[:-1] + resource = StaticResource( + prefix, + path, + name=name, + expect_handler=expect_handler, + chunk_size=chunk_size, + show_index=show_index, + follow_symlinks=follow_symlinks, + append_version=append_version, + ) + self.register_resource(resource) + return resource + + def add_head(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method HEAD.""" + return self.add_route(hdrs.METH_HEAD, path, handler, **kwargs) + + def add_options(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method OPTIONS.""" + return self.add_route(hdrs.METH_OPTIONS, path, handler, **kwargs) + + def add_get( + self, + path: str, + handler: Handler, + *, + name: Optional[str] = None, + allow_head: bool = True, + **kwargs: Any, + ) -> AbstractRoute: + """Shortcut for add_route with method GET. + + If allow_head is true, another + route is added allowing head requests to the same endpoint. + """ + resource = self.add_resource(path, name=name) + if allow_head: + resource.add_route(hdrs.METH_HEAD, handler, **kwargs) + return resource.add_route(hdrs.METH_GET, handler, **kwargs) + + def add_post(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method POST.""" + return self.add_route(hdrs.METH_POST, path, handler, **kwargs) + + def add_put(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method PUT.""" + return self.add_route(hdrs.METH_PUT, path, handler, **kwargs) + + def add_patch(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method PATCH.""" + return self.add_route(hdrs.METH_PATCH, path, handler, **kwargs) + + def add_delete(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method DELETE.""" + return self.add_route(hdrs.METH_DELETE, path, handler, **kwargs) + + def add_view( + self, path: str, handler: Type[AbstractView], **kwargs: Any + ) -> AbstractRoute: + """Shortcut for add_route with ANY methods for a class-based view.""" + return self.add_route(hdrs.METH_ANY, path, handler, **kwargs) + + def freeze(self) -> None: + super().freeze() + for resource in self._resources: + resource.freeze() + + def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]: + """Append routes to route table. + + Parameter should be a sequence of RouteDef objects. + + Returns a list of registered AbstractRoute instances. + """ + registered_routes = [] + for route_def in routes: + registered_routes.extend(route_def.register(self)) + return registered_routes + + +def _quote_path(value: str) -> str: + if YARL_VERSION < (1, 6): + value = value.replace("%", "%25") + return URL.build(path=value, encoded=False).raw_path + + +def _unquote_path(value: str) -> str: + return URL.build(path=value, encoded=True).path + + +def _requote_path(value: str) -> str: + # Quote non-ascii characters and other characters which must be quoted, + # but preserve existing %-sequences. + result = _quote_path(value) + if "%" in value: + result = result.replace("%25", "%") + return result diff --git a/lib/aiohttp/web_ws.py b/lib/aiohttp/web_ws.py new file mode 100644 index 0000000..0d32a21 --- /dev/null +++ b/lib/aiohttp/web_ws.py @@ -0,0 +1,487 @@ +import asyncio +import base64 +import binascii +import hashlib +import json +from typing import Any, Iterable, Optional, Tuple, cast + +import async_timeout +import attr +from multidict import CIMultiDict + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import call_later, set_result +from .http import ( + WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE, + WS_KEY, + WebSocketError, + WebSocketReader, + WebSocketWriter, + WSCloseCode, + WSMessage, + WSMsgType as WSMsgType, + ws_ext_gen, + ws_ext_parse, +) +from .log import ws_logger +from .streams import EofStream, FlowControlDataQueue +from .typedefs import Final, JSONDecoder, JSONEncoder +from .web_exceptions import HTTPBadRequest, HTTPException +from .web_request import BaseRequest +from .web_response import StreamResponse + +__all__ = ( + "WebSocketResponse", + "WebSocketReady", + "WSMsgType", +) + +THRESHOLD_CONNLOST_ACCESS: Final[int] = 5 + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class WebSocketReady: + ok: bool + protocol: Optional[str] + + def __bool__(self) -> bool: + return self.ok + + +class WebSocketResponse(StreamResponse): + + _length_check = False + + def __init__( + self, + *, + timeout: float = 10.0, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + protocols: Iterable[str] = (), + compress: bool = True, + max_msg_size: int = 4 * 1024 * 1024, + ) -> None: + super().__init__(status=101) + self._protocols = protocols + self._ws_protocol: Optional[str] = None + self._writer: Optional[WebSocketWriter] = None + self._reader: Optional[FlowControlDataQueue[WSMessage]] = None + self._closed = False + self._closing = False + self._conn_lost = 0 + self._close_code: Optional[int] = None + self._loop: Optional[asyncio.AbstractEventLoop] = None + self._waiting: Optional[asyncio.Future[bool]] = None + self._exception: Optional[BaseException] = None + self._timeout = timeout + self._receive_timeout = receive_timeout + self._autoclose = autoclose + self._autoping = autoping + self._heartbeat = heartbeat + self._heartbeat_cb: Optional[asyncio.TimerHandle] = None + if heartbeat is not None: + self._pong_heartbeat = heartbeat / 2.0 + self._pong_response_cb: Optional[asyncio.TimerHandle] = None + self._compress = compress + self._max_msg_size = max_msg_size + + def _cancel_heartbeat(self) -> None: + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = None + + if self._heartbeat_cb is not None: + self._heartbeat_cb.cancel() + self._heartbeat_cb = None + + def _reset_heartbeat(self) -> None: + self._cancel_heartbeat() + + if self._heartbeat is not None: + assert self._loop is not None + self._heartbeat_cb = call_later( + self._send_heartbeat, self._heartbeat, self._loop + ) + + def _send_heartbeat(self) -> None: + if self._heartbeat is not None and not self._closed: + assert self._loop is not None + # fire-and-forget a task is not perfect but maybe ok for + # sending ping. Otherwise we need a long-living heartbeat + # task in the class. + self._loop.create_task(self._writer.ping()) # type: ignore[union-attr] + + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = call_later( + self._pong_not_received, self._pong_heartbeat, self._loop + ) + + def _pong_not_received(self) -> None: + if self._req is not None and self._req.transport is not None: + self._closed = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = asyncio.TimeoutError() + self._req.transport.close() + + async def prepare(self, request: BaseRequest) -> AbstractStreamWriter: + # make pre-check to don't hide it by do_handshake() exceptions + if self._payload_writer is not None: + return self._payload_writer + + protocol, writer = self._pre_start(request) + payload_writer = await super().prepare(request) + assert payload_writer is not None + self._post_start(request, protocol, writer) + await payload_writer.drain() + return payload_writer + + def _handshake( + self, request: BaseRequest + ) -> Tuple["CIMultiDict[str]", str, bool, bool]: + headers = request.headers + if "websocket" != headers.get(hdrs.UPGRADE, "").lower().strip(): + raise HTTPBadRequest( + text=( + "No WebSocket UPGRADE hdr: {}\n Can " + '"Upgrade" only to "WebSocket".' + ).format(headers.get(hdrs.UPGRADE)) + ) + + if "upgrade" not in headers.get(hdrs.CONNECTION, "").lower(): + raise HTTPBadRequest( + text="No CONNECTION upgrade hdr: {}".format( + headers.get(hdrs.CONNECTION) + ) + ) + + # find common sub-protocol between client and server + protocol = None + if hdrs.SEC_WEBSOCKET_PROTOCOL in headers: + req_protocols = [ + str(proto.strip()) + for proto in headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") + ] + + for proto in req_protocols: + if proto in self._protocols: + protocol = proto + break + else: + # No overlap found: Return no protocol as per spec + ws_logger.warning( + "Client protocols %r don’t overlap server-known ones %r", + req_protocols, + self._protocols, + ) + + # check supported version + version = headers.get(hdrs.SEC_WEBSOCKET_VERSION, "") + if version not in ("13", "8", "7"): + raise HTTPBadRequest(text=f"Unsupported version: {version}") + + # check client handshake for validity + key = headers.get(hdrs.SEC_WEBSOCKET_KEY) + try: + if not key or len(base64.b64decode(key)) != 16: + raise HTTPBadRequest(text=f"Handshake error: {key!r}") + except binascii.Error: + raise HTTPBadRequest(text=f"Handshake error: {key!r}") from None + + accept_val = base64.b64encode( + hashlib.sha1(key.encode() + WS_KEY).digest() + ).decode() + response_headers = CIMultiDict( + { + hdrs.UPGRADE: "websocket", + hdrs.CONNECTION: "upgrade", + hdrs.SEC_WEBSOCKET_ACCEPT: accept_val, + } + ) + + notakeover = False + compress = 0 + if self._compress: + extensions = headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS) + # Server side always get return with no exception. + # If something happened, just drop compress extension + compress, notakeover = ws_ext_parse(extensions, isserver=True) + if compress: + enabledext = ws_ext_gen( + compress=compress, isserver=True, server_notakeover=notakeover + ) + response_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = enabledext + + if protocol: + response_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = protocol + return ( + response_headers, + protocol, + compress, + notakeover, + ) # type: ignore[return-value] + + def _pre_start(self, request: BaseRequest) -> Tuple[str, WebSocketWriter]: + self._loop = request._loop + + headers, protocol, compress, notakeover = self._handshake(request) + + self.set_status(101) + self.headers.update(headers) + self.force_close() + self._compress = compress + transport = request._protocol.transport + assert transport is not None + writer = WebSocketWriter( + request._protocol, transport, compress=compress, notakeover=notakeover + ) + + return protocol, writer + + def _post_start( + self, request: BaseRequest, protocol: str, writer: WebSocketWriter + ) -> None: + self._ws_protocol = protocol + self._writer = writer + + self._reset_heartbeat() + + loop = self._loop + assert loop is not None + self._reader = FlowControlDataQueue(request._protocol, 2**16, loop=loop) + request.protocol.set_parser( + WebSocketReader(self._reader, self._max_msg_size, compress=self._compress) + ) + # disable HTTP keepalive for WebSocket + request.protocol.keep_alive(False) + + def can_prepare(self, request: BaseRequest) -> WebSocketReady: + if self._writer is not None: + raise RuntimeError("Already started") + try: + _, protocol, _, _ = self._handshake(request) + except HTTPException: + return WebSocketReady(False, None) + else: + return WebSocketReady(True, protocol) + + @property + def closed(self) -> bool: + return self._closed + + @property + def close_code(self) -> Optional[int]: + return self._close_code + + @property + def ws_protocol(self) -> Optional[str]: + return self._ws_protocol + + @property + def compress(self) -> bool: + return self._compress + + def exception(self) -> Optional[BaseException]: + return self._exception + + async def ping(self, message: bytes = b"") -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + await self._writer.ping(message) + + async def pong(self, message: bytes = b"") -> None: + # unsolicited pong + if self._writer is None: + raise RuntimeError("Call .prepare() first") + await self._writer.pong(message) + + async def send_str(self, data: str, compress: Optional[bool] = None) -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + if not isinstance(data, str): + raise TypeError("data argument must be str (%r)" % type(data)) + await self._writer.send(data, binary=False, compress=compress) + + async def send_bytes(self, data: bytes, compress: Optional[bool] = None) -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + if not isinstance(data, (bytes, bytearray, memoryview)): + raise TypeError("data argument must be byte-ish (%r)" % type(data)) + await self._writer.send(data, binary=True, compress=compress) + + async def send_json( + self, + data: Any, + compress: Optional[bool] = None, + *, + dumps: JSONEncoder = json.dumps, + ) -> None: + await self.send_str(dumps(data), compress=compress) + + async def write_eof(self) -> None: # type: ignore[override] + if self._eof_sent: + return + if self._payload_writer is None: + raise RuntimeError("Response has not been started") + + await self.close() + self._eof_sent = True + + async def close(self, *, code: int = WSCloseCode.OK, message: bytes = b"") -> bool: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + + self._cancel_heartbeat() + reader = self._reader + assert reader is not None + + # we need to break `receive()` cycle first, + # `close()` may be called from different task + if self._waiting is not None and not self._closed: + reader.feed_data(WS_CLOSING_MESSAGE, 0) + await self._waiting + + if not self._closed: + self._closed = True + try: + await self._writer.close(code, message) + writer = self._payload_writer + assert writer is not None + await writer.drain() + except (asyncio.CancelledError, asyncio.TimeoutError): + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + return True + + if self._closing: + return True + + reader = self._reader + assert reader is not None + try: + async with async_timeout.timeout(self._timeout): + msg = await reader.read() + except asyncio.CancelledError: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + return True + + if msg.type == WSMsgType.CLOSE: + self._close_code = msg.data + return True + + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = asyncio.TimeoutError() + return True + else: + return False + + async def receive(self, timeout: Optional[float] = None) -> WSMessage: + if self._reader is None: + raise RuntimeError("Call .prepare() first") + + loop = self._loop + assert loop is not None + while True: + if self._waiting is not None: + raise RuntimeError("Concurrent call to receive() is not allowed") + + if self._closed: + self._conn_lost += 1 + if self._conn_lost >= THRESHOLD_CONNLOST_ACCESS: + raise RuntimeError("WebSocket connection is closed.") + return WS_CLOSED_MESSAGE + elif self._closing: + return WS_CLOSING_MESSAGE + + try: + self._waiting = loop.create_future() + try: + async with async_timeout.timeout(timeout or self._receive_timeout): + msg = await self._reader.read() + self._reset_heartbeat() + finally: + waiter = self._waiting + set_result(waiter, True) + self._waiting = None + except (asyncio.CancelledError, asyncio.TimeoutError): + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except EofStream: + self._close_code = WSCloseCode.OK + await self.close() + return WSMessage(WSMsgType.CLOSED, None, None) + except WebSocketError as exc: + self._close_code = exc.code + await self.close(code=exc.code) + return WSMessage(WSMsgType.ERROR, exc, None) + except Exception as exc: + self._exception = exc + self._closing = True + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + await self.close() + return WSMessage(WSMsgType.ERROR, exc, None) + + if msg.type == WSMsgType.CLOSE: + self._closing = True + self._close_code = msg.data + if not self._closed and self._autoclose: + await self.close() + elif msg.type == WSMsgType.CLOSING: + self._closing = True + elif msg.type == WSMsgType.PING and self._autoping: + await self.pong(msg.data) + continue + elif msg.type == WSMsgType.PONG and self._autoping: + continue + + return msg + + async def receive_str(self, *, timeout: Optional[float] = None) -> str: + msg = await self.receive(timeout) + if msg.type != WSMsgType.TEXT: + raise TypeError( + "Received message {}:{!r} is not WSMsgType.TEXT".format( + msg.type, msg.data + ) + ) + return cast(str, msg.data) + + async def receive_bytes(self, *, timeout: Optional[float] = None) -> bytes: + msg = await self.receive(timeout) + if msg.type != WSMsgType.BINARY: + raise TypeError(f"Received message {msg.type}:{msg.data!r} is not bytes") + return cast(bytes, msg.data) + + async def receive_json( + self, *, loads: JSONDecoder = json.loads, timeout: Optional[float] = None + ) -> Any: + data = await self.receive_str(timeout=timeout) + return loads(data) + + async def write(self, data: bytes) -> None: + raise RuntimeError("Cannot call .write() for websocket") + + def __aiter__(self) -> "WebSocketResponse": + return self + + async def __anext__(self) -> WSMessage: + msg = await self.receive() + if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED): + raise StopAsyncIteration + return msg + + def _cancel(self, exc: BaseException) -> None: + if self._reader is not None: + self._reader.set_exception(exc) diff --git a/lib/aiohttp/worker.py b/lib/aiohttp/worker.py new file mode 100644 index 0000000..f130289 --- /dev/null +++ b/lib/aiohttp/worker.py @@ -0,0 +1,269 @@ +"""Async gunicorn worker for aiohttp.web""" + +import asyncio +import os +import re +import signal +import sys +from types import FrameType +from typing import Any, Awaitable, Callable, Optional, Union # noqa + +from gunicorn.config import AccessLogFormat as GunicornAccessLogFormat +from gunicorn.workers import base + +from aiohttp import web + +from .helpers import set_result +from .web_app import Application +from .web_log import AccessLogger + +try: + import ssl + + SSLContext = ssl.SSLContext +except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ("GunicornWebWorker", "GunicornUVLoopWebWorker", "GunicornTokioWebWorker") + + +class GunicornWebWorker(base.Worker): # type: ignore[misc,no-any-unimported] + + DEFAULT_AIOHTTP_LOG_FORMAT = AccessLogger.LOG_FORMAT + DEFAULT_GUNICORN_LOG_FORMAT = GunicornAccessLogFormat.default + + def __init__(self, *args: Any, **kw: Any) -> None: # pragma: no cover + super().__init__(*args, **kw) + + self._task: Optional[asyncio.Task[None]] = None + self.exit_code = 0 + self._notify_waiter: Optional[asyncio.Future[bool]] = None + + def init_process(self) -> None: + # create new event_loop after fork + asyncio.get_event_loop().close() + + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + + super().init_process() + + def run(self) -> None: + self._task = self.loop.create_task(self._run()) + + try: # ignore all finalization problems + self.loop.run_until_complete(self._task) + except Exception: + self.log.exception("Exception in gunicorn worker") + self.loop.run_until_complete(self.loop.shutdown_asyncgens()) + self.loop.close() + + sys.exit(self.exit_code) + + async def _run(self) -> None: + runner = None + if isinstance(self.wsgi, Application): + app = self.wsgi + elif asyncio.iscoroutinefunction(self.wsgi): + wsgi = await self.wsgi() + if isinstance(wsgi, web.AppRunner): + runner = wsgi + app = runner.app + else: + app = wsgi + else: + raise RuntimeError( + "wsgi app should be either Application or " + "async function returning Application, got {}".format(self.wsgi) + ) + + if runner is None: + access_log = self.log.access_log if self.cfg.accesslog else None + runner = web.AppRunner( + app, + logger=self.log, + keepalive_timeout=self.cfg.keepalive, + access_log=access_log, + access_log_format=self._get_valid_log_format( + self.cfg.access_log_format + ), + ) + await runner.setup() + + ctx = self._create_ssl_context(self.cfg) if self.cfg.is_ssl else None + + runner = runner + assert runner is not None + server = runner.server + assert server is not None + for sock in self.sockets: + site = web.SockSite( + runner, + sock, + ssl_context=ctx, + shutdown_timeout=self.cfg.graceful_timeout / 100 * 95, + ) + await site.start() + + # If our parent changed then we shut down. + pid = os.getpid() + try: + while self.alive: # type: ignore[has-type] + self.notify() + + cnt = server.requests_count + if self.cfg.max_requests and cnt > self.cfg.max_requests: + self.alive = False + self.log.info("Max requests, shutting down: %s", self) + + elif pid == os.getpid() and self.ppid != os.getppid(): + self.alive = False + self.log.info("Parent changed, shutting down: %s", self) + else: + await self._wait_next_notify() + except BaseException: + pass + + await runner.cleanup() + + def _wait_next_notify(self) -> "asyncio.Future[bool]": + self._notify_waiter_done() + + loop = self.loop + assert loop is not None + self._notify_waiter = waiter = loop.create_future() + self.loop.call_later(1.0, self._notify_waiter_done, waiter) + + return waiter + + def _notify_waiter_done( + self, waiter: Optional["asyncio.Future[bool]"] = None + ) -> None: + if waiter is None: + waiter = self._notify_waiter + if waiter is not None: + set_result(waiter, True) + + if waiter is self._notify_waiter: + self._notify_waiter = None + + def init_signals(self) -> None: + # Set up signals through the event loop API. + + self.loop.add_signal_handler( + signal.SIGQUIT, self.handle_quit, signal.SIGQUIT, None + ) + + self.loop.add_signal_handler( + signal.SIGTERM, self.handle_exit, signal.SIGTERM, None + ) + + self.loop.add_signal_handler( + signal.SIGINT, self.handle_quit, signal.SIGINT, None + ) + + self.loop.add_signal_handler( + signal.SIGWINCH, self.handle_winch, signal.SIGWINCH, None + ) + + self.loop.add_signal_handler( + signal.SIGUSR1, self.handle_usr1, signal.SIGUSR1, None + ) + + self.loop.add_signal_handler( + signal.SIGABRT, self.handle_abort, signal.SIGABRT, None + ) + + # Don't let SIGTERM and SIGUSR1 disturb active requests + # by interrupting system calls + signal.siginterrupt(signal.SIGTERM, False) + signal.siginterrupt(signal.SIGUSR1, False) + # Reset signals so Gunicorn doesn't swallow subprocess return codes + # See: https://github.com/aio-libs/aiohttp/issues/6130 + if sys.version_info < (3, 8): + # Starting from Python 3.8, + # the default child watcher is ThreadedChildWatcher. + # The watcher doesn't depend on SIGCHLD signal, + # there is no need to reset it. + signal.signal(signal.SIGCHLD, signal.SIG_DFL) + + def handle_quit(self, sig: int, frame: FrameType) -> None: + self.alive = False + + # worker_int callback + self.cfg.worker_int(self) + + # wakeup closing process + self._notify_waiter_done() + + def handle_abort(self, sig: int, frame: FrameType) -> None: + self.alive = False + self.exit_code = 1 + self.cfg.worker_abort(self) + sys.exit(1) + + @staticmethod + def _create_ssl_context(cfg: Any) -> "SSLContext": + """Creates SSLContext instance for usage in asyncio.create_server. + + See ssl.SSLSocket.__init__ for more details. + """ + if ssl is None: # pragma: no cover + raise RuntimeError("SSL is not supported.") + + ctx = ssl.SSLContext(cfg.ssl_version) + ctx.load_cert_chain(cfg.certfile, cfg.keyfile) + ctx.verify_mode = cfg.cert_reqs + if cfg.ca_certs: + ctx.load_verify_locations(cfg.ca_certs) + if cfg.ciphers: + ctx.set_ciphers(cfg.ciphers) + return ctx + + def _get_valid_log_format(self, source_format: str) -> str: + if source_format == self.DEFAULT_GUNICORN_LOG_FORMAT: + return self.DEFAULT_AIOHTTP_LOG_FORMAT + elif re.search(r"%\([^\)]+\)", source_format): + raise ValueError( + "Gunicorn's style options in form of `%(name)s` are not " + "supported for the log formatting. Please use aiohttp's " + "format specification to configure access log formatting: " + "http://docs.aiohttp.org/en/stable/logging.html" + "#format-specification" + ) + else: + return source_format + + +class GunicornUVLoopWebWorker(GunicornWebWorker): + def init_process(self) -> None: + import uvloop + + # Close any existing event loop before setting a + # new policy. + asyncio.get_event_loop().close() + + # Setup uvloop policy, so that every + # asyncio.get_event_loop() will create an instance + # of uvloop event loop. + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + + super().init_process() + + +class GunicornTokioWebWorker(GunicornWebWorker): + def init_process(self) -> None: # pragma: no cover + import tokio + + # Close any existing event loop before setting a + # new policy. + asyncio.get_event_loop().close() + + # Setup tokio policy, so that every + # asyncio.get_event_loop() will create an instance + # of tokio event loop. + asyncio.set_event_loop_policy(tokio.EventLoopPolicy()) + + super().init_process() diff --git a/lib/aiosignal-1.3.1.dist-info/INSTALLER b/lib/aiosignal-1.3.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/aiosignal-1.3.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/aiosignal-1.3.1.dist-info/LICENSE b/lib/aiosignal-1.3.1.dist-info/LICENSE new file mode 100644 index 0000000..7082a2d --- /dev/null +++ b/lib/aiosignal-1.3.1.dist-info/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013-2019 Nikolay Kim and Andrew Svetlov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/aiosignal-1.3.1.dist-info/METADATA b/lib/aiosignal-1.3.1.dist-info/METADATA new file mode 100644 index 0000000..fc96452 --- /dev/null +++ b/lib/aiosignal-1.3.1.dist-info/METADATA @@ -0,0 +1,128 @@ +Metadata-Version: 2.1 +Name: aiosignal +Version: 1.3.1 +Summary: aiosignal: a list of registered asynchronous callbacks +Home-page: https://github.com/aio-libs/aiosignal +Maintainer: aiohttp team +Maintainer-email: team@aiohttp.org +License: Apache 2.0 +Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby +Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiosignal/actions +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/aiosignal +Project-URL: Docs: RTD, https://docs.aiosignal.org +Project-URL: GitHub: issues, https://github.com/aio-libs/aiosignal/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/aiosignal +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Framework :: AsyncIO +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: frozenlist (>=1.1.0) + +========= +aiosignal +========= + +.. image:: https://github.com/aio-libs/aiosignal/workflows/CI/badge.svg + :target: https://github.com/aio-libs/aiosignal/actions?query=workflow%3ACI + :alt: GitHub status for master branch + +.. image:: https://codecov.io/gh/aio-libs/aiosignal/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/aiosignal + :alt: codecov.io status for master branch + +.. image:: https://badge.fury.io/py/aiosignal.svg + :target: https://pypi.org/project/aiosignal + :alt: Latest PyPI package version + +.. image:: https://readthedocs.org/projects/aiosignal/badge/?version=latest + :target: https://aiosignal.readthedocs.io/ + :alt: Latest Read The Docs + +.. image:: https://img.shields.io/discourse/topics?server=https%3A%2F%2Faio-libs.discourse.group%2F + :target: https://aio-libs.discourse.group/ + :alt: Discourse group for io-libs + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :target: https://gitter.im/aio-libs/Lobby + :alt: Chat on Gitter + +Introduction +============ + +A project to manage callbacks in `asyncio` projects. + +``Signal`` is a list of registered asynchronous callbacks. + +The signal's life-cycle has two stages: after creation its content +could be filled by using standard list operations: ``sig.append()`` +etc. + +After you call ``sig.freeze()`` the signal is *frozen*: adding, removing +and dropping callbacks is forbidden. + +The only available operation is calling the previously registered +callbacks by using ``await sig.send(data)``. + +For concrete usage examples see the `Signals + +section of the `Web Server Advanced +` chapter of the `aiohttp +documentation`_. + + +Installation +------------ + +:: + + $ pip install aiosignal + +The library requires Python 3.6 or newer. + + +Documentation +============= + +https://aiosignal.readthedocs.io/ + +Communication channels +====================== + +*gitter chat* https://gitter.im/aio-libs/Lobby + +Requirements +============ + +- Python >= 3.6 +- frozenlist >= 1.0.0 + +License +======= + +``aiosignal`` is offered under the Apache 2 license. + +Source code +=========== + +The project is hosted on GitHub_ + +Please file an issue in the `bug tracker +`_ if you have found a bug +or have some suggestions to improve the library. + +.. _GitHub: https://github.com/aio-libs/aiosignal +.. _aiohttp documentation: https://docs.aiohttp.org/ diff --git a/lib/aiosignal-1.3.1.dist-info/RECORD b/lib/aiosignal-1.3.1.dist-info/RECORD new file mode 100644 index 0000000..5f4d37d --- /dev/null +++ b/lib/aiosignal-1.3.1.dist-info/RECORD @@ -0,0 +1,10 @@ +aiosignal-1.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +aiosignal-1.3.1.dist-info/LICENSE,sha256=b9UkPpLdf5jsacesN3co50kFcJ_1J6W_mNbQJjwE9bY,11332 +aiosignal-1.3.1.dist-info/METADATA,sha256=c0HRnlYzfXKztZPTFDlPfygizTherhG5WdwXlvco0Ug,4008 +aiosignal-1.3.1.dist-info/RECORD,, +aiosignal-1.3.1.dist-info/WHEEL,sha256=ZL1lC_LiPDNRgDnOl2taCMc83aPEUZgHHv2h-LDgdiM,92 +aiosignal-1.3.1.dist-info/top_level.txt,sha256=z45aNOKGDdrI1roqZY3BGXQ22kJFPHBmVdwtLYLtXC0,10 +aiosignal/__init__.py,sha256=zQNfFYRSd84bswvpFv8ZWjEr5DeYwV3LXbMSyo2222s,867 +aiosignal/__init__.pyi,sha256=xeCddYSS8fZAkz8S4HuKSR2IDe3N7RW_LKcXDPPA1Xk,311 +aiosignal/__pycache__/__init__.cpython-39.pyc,, +aiosignal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/lib/aiosignal-1.3.1.dist-info/WHEEL b/lib/aiosignal-1.3.1.dist-info/WHEEL new file mode 100644 index 0000000..5e1f087 --- /dev/null +++ b/lib/aiosignal-1.3.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lib/aiosignal-1.3.1.dist-info/top_level.txt b/lib/aiosignal-1.3.1.dist-info/top_level.txt new file mode 100644 index 0000000..ac6df3a --- /dev/null +++ b/lib/aiosignal-1.3.1.dist-info/top_level.txt @@ -0,0 +1 @@ +aiosignal diff --git a/lib/aiosignal/__init__.py b/lib/aiosignal/__init__.py new file mode 100644 index 0000000..3d288e6 --- /dev/null +++ b/lib/aiosignal/__init__.py @@ -0,0 +1,36 @@ +from frozenlist import FrozenList + +__version__ = "1.3.1" + +__all__ = ("Signal",) + + +class Signal(FrozenList): + """Coroutine-based signal implementation. + + To connect a callback to a signal, use any list method. + + Signals are fired using the send() coroutine, which takes named + arguments. + """ + + __slots__ = ("_owner",) + + def __init__(self, owner): + super().__init__() + self._owner = owner + + def __repr__(self): + return "".format( + self._owner, self.frozen, list(self) + ) + + async def send(self, *args, **kwargs): + """ + Sends data to all registered receivers. + """ + if not self.frozen: + raise RuntimeError("Cannot send non-frozen signal.") + + for receiver in self: + await receiver(*args, **kwargs) # type: ignore diff --git a/lib/aiosignal/__init__.pyi b/lib/aiosignal/__init__.pyi new file mode 100644 index 0000000..d4e3416 --- /dev/null +++ b/lib/aiosignal/__init__.pyi @@ -0,0 +1,12 @@ +from typing import Any, Generic, TypeVar + +from frozenlist import FrozenList + +__all__ = ("Signal",) + +_T = TypeVar("_T") + +class Signal(FrozenList[_T], Generic[_T]): + def __init__(self, owner: Any) -> None: ... + def __repr__(self) -> str: ... + async def send(self, *args: Any, **kwargs: Any) -> None: ... diff --git a/lib/aiosignal/__pycache__/__init__.cpython-39.pyc b/lib/aiosignal/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f51485788f6a5dab31aa339774a4cfcb4677356 GIT binary patch literal 1324 zcmZ8h&5j&35VpJBGrhC38w3$Q5<-SkTG|~*!X+p~NE--|qD5li(i&uW+n$-u_Sh5K z!zz>63%gQoJVPRJ$$>Y>J8v>|g^Qc6@(CMqq{2ShQIT@aONdY+a{^^Qo<{{yT^LEFJs&aSk1 zRQs^XZjNq`Zo4ZcTBZ<-Jtq^YQpGOvGE422{P2yj0OV^<2%bMYmBY} zm%Nfqb1bV99xQ0xwLQM{noIMPH;@LO>oBuwbGggJbLljn)DA)|eQl;Z%ry7fs2c@1 z-(cC}r?a}6@gPsM=SI%8iU6dWE)kR;MHLGq?5WYNO1gSPiF=%wpuY|xkaO~az9f{a z>6(Z{5hwsXEYddf%Y}ArE<|nWAcVsv7D>yzZYFIOeZQjZmJn4Vy%*vY`SY8D&&J<- z?fm$ta`j?-U;C3_7vt$72tSibyK&Gy+!{ZskH@|adVc|Bl2h%+vbJ5JKE4b%T0C_< zi1`*`#o&Yv=;~TmtkI=aMe$@aaF16GVEVT~DEfk|$zw?EaT3xsIj1jENH95EeRQ{T z#A70#KReswlgP5TefFU{yH~KbH?eLmL(CnNXwWk3U_o6U0=Vse#1M8^E~ghxh<8Bq zh=ou_cmKMAyF336ZpR zuc2*E-c$)>l&fty(s?9}4kgDVxnnUkGI@_zcwZjSWD($P@Pl_;pC? zc4tG%N;eIh{@bH1G{Hv2Cyq!w{<(6>a+VBPp2hEnMOF^F8epp(zL<7f2rL^sc?b;{ d;EjW8d`MUP$4DI!?so9+Atxgdh6zhp`Zq46PRIZN literal 0 HcmV?d00001 diff --git a/lib/aiosignal/py.typed b/lib/aiosignal/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lib/async_timeout-4.0.2.dist-info/INSTALLER b/lib/async_timeout-4.0.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/async_timeout-4.0.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/async_timeout-4.0.2.dist-info/LICENSE b/lib/async_timeout-4.0.2.dist-info/LICENSE new file mode 100644 index 0000000..033c86b --- /dev/null +++ b/lib/async_timeout-4.0.2.dist-info/LICENSE @@ -0,0 +1,13 @@ +Copyright 2016-2020 aio-libs collaboration. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/lib/async_timeout-4.0.2.dist-info/METADATA b/lib/async_timeout-4.0.2.dist-info/METADATA new file mode 100644 index 0000000..e68d234 --- /dev/null +++ b/lib/async_timeout-4.0.2.dist-info/METADATA @@ -0,0 +1,133 @@ +Metadata-Version: 2.1 +Name: async-timeout +Version: 4.0.2 +Summary: Timeout context manager for asyncio programs +Home-page: https://github.com/aio-libs/async-timeout +Author: Andrew Svetlov +Author-email: andrew.svetlov@gmail.com +License: Apache 2 +Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby +Project-URL: CI: GitHub Actions, https://github.com/aio-libs/async-timeout/actions +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/async-timeout +Project-URL: GitHub: issues, https://github.com/aio-libs/async-timeout/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/async-timeout +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Topic :: Software Development :: Libraries +Classifier: Framework :: AsyncIO +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: typing-extensions (>=3.6.5) ; python_version < "3.8" + +async-timeout +============= +.. image:: https://travis-ci.com/aio-libs/async-timeout.svg?branch=master + :target: https://travis-ci.com/aio-libs/async-timeout +.. image:: https://codecov.io/gh/aio-libs/async-timeout/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/async-timeout +.. image:: https://img.shields.io/pypi/v/async-timeout.svg + :target: https://pypi.python.org/pypi/async-timeout +.. image:: https://badges.gitter.im/Join%20Chat.svg + :target: https://gitter.im/aio-libs/Lobby + :alt: Chat on Gitter + +asyncio-compatible timeout context manager. + + +Usage example +------------- + + +The context manager is useful in cases when you want to apply timeout +logic around block of code or in cases when ``asyncio.wait_for()`` is +not suitable. Also it's much faster than ``asyncio.wait_for()`` +because ``timeout`` doesn't create a new task. + +The ``timeout(delay, *, loop=None)`` call returns a context manager +that cancels a block on *timeout* expiring:: + + async with timeout(1.5): + await inner() + +1. If ``inner()`` is executed faster than in ``1.5`` seconds nothing + happens. +2. Otherwise ``inner()`` is cancelled internally by sending + ``asyncio.CancelledError`` into but ``asyncio.TimeoutError`` is + raised outside of context manager scope. + +*timeout* parameter could be ``None`` for skipping timeout functionality. + + +Alternatively, ``timeout_at(when)`` can be used for scheduling +at the absolute time:: + + loop = asyncio.get_event_loop() + now = loop.time() + + async with timeout_at(now + 1.5): + await inner() + + +Please note: it is not POSIX time but a time with +undefined starting base, e.g. the time of the system power on. + + +Context manager has ``.expired`` property for check if timeout happens +exactly in context manager:: + + async with timeout(1.5) as cm: + await inner() + print(cm.expired) + +The property is ``True`` if ``inner()`` execution is cancelled by +timeout context manager. + +If ``inner()`` call explicitly raises ``TimeoutError`` ``cm.expired`` +is ``False``. + +The scheduled deadline time is available as ``.deadline`` property:: + + async with timeout(1.5) as cm: + cm.deadline + +Not finished yet timeout can be rescheduled by ``shift_by()`` +or ``shift_to()`` methods:: + + async with timeout(1.5) as cm: + cm.shift(1) # add another second on waiting + cm.update(loop.time() + 5) # reschedule to now+5 seconds + +Rescheduling is forbidden if the timeout is expired or after exit from ``async with`` +code block. + + +Installation +------------ + +:: + + $ pip install async-timeout + +The library is Python 3 only! + + + +Authors and License +------------------- + +The module is written by Andrew Svetlov. + +It's *Apache 2* licensed and freely available. + + diff --git a/lib/async_timeout-4.0.2.dist-info/RECORD b/lib/async_timeout-4.0.2.dist-info/RECORD new file mode 100644 index 0000000..8222d6a --- /dev/null +++ b/lib/async_timeout-4.0.2.dist-info/RECORD @@ -0,0 +1,10 @@ +async_timeout-4.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +async_timeout-4.0.2.dist-info/LICENSE,sha256=4Y17uPUT4sRrtYXJS1hb0wcg3TzLId2weG9y0WZY-Sw,568 +async_timeout-4.0.2.dist-info/METADATA,sha256=2pfMxxBst5vQ7SfMy5TDaDU0cRgCSQa7wcD5eI-Ew-8,4193 +async_timeout-4.0.2.dist-info/RECORD,, +async_timeout-4.0.2.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +async_timeout-4.0.2.dist-info/top_level.txt,sha256=9oM4e7Twq8iD_7_Q3Mz0E6GPIB6vJvRFo-UBwUQtBDU,14 +async_timeout-4.0.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +async_timeout/__init__.py,sha256=N-JUI_VExhHnO0emkF_-h08dl4HBgOje16N4Ci-W-go,7487 +async_timeout/__pycache__/__init__.cpython-39.pyc,, +async_timeout/py.typed,sha256=tyozzRT1fziXETDxokmuyt6jhOmtjUbnVNJdZcG7ik0,12 diff --git a/lib/async_timeout-4.0.2.dist-info/WHEEL b/lib/async_timeout-4.0.2.dist-info/WHEEL new file mode 100644 index 0000000..5bad85f --- /dev/null +++ b/lib/async_timeout-4.0.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lib/async_timeout-4.0.2.dist-info/top_level.txt b/lib/async_timeout-4.0.2.dist-info/top_level.txt new file mode 100644 index 0000000..ad29955 --- /dev/null +++ b/lib/async_timeout-4.0.2.dist-info/top_level.txt @@ -0,0 +1 @@ +async_timeout diff --git a/lib/async_timeout-4.0.2.dist-info/zip-safe b/lib/async_timeout-4.0.2.dist-info/zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/async_timeout-4.0.2.dist-info/zip-safe @@ -0,0 +1 @@ + diff --git a/lib/async_timeout/__init__.py b/lib/async_timeout/__init__.py new file mode 100644 index 0000000..179d1b0 --- /dev/null +++ b/lib/async_timeout/__init__.py @@ -0,0 +1,247 @@ +import asyncio +import enum +import sys +import warnings +from types import TracebackType +from typing import Any, Optional, Type + + +if sys.version_info >= (3, 8): + from typing import final +else: + from typing_extensions import final + + +__version__ = "4.0.2" + + +__all__ = ("timeout", "timeout_at", "Timeout") + + +def timeout(delay: Optional[float]) -> "Timeout": + """timeout context manager. + + Useful in cases when you want to apply timeout logic around block + of code or in cases when asyncio.wait_for is not suitable. For example: + + >>> async with timeout(0.001): + ... async with aiohttp.get('https://github.com') as r: + ... await r.text() + + + delay - value in seconds or None to disable timeout logic + """ + loop = _get_running_loop() + if delay is not None: + deadline = loop.time() + delay # type: Optional[float] + else: + deadline = None + return Timeout(deadline, loop) + + +def timeout_at(deadline: Optional[float]) -> "Timeout": + """Schedule the timeout at absolute time. + + deadline argument points on the time in the same clock system + as loop.time(). + + Please note: it is not POSIX time but a time with + undefined starting base, e.g. the time of the system power on. + + >>> async with timeout_at(loop.time() + 10): + ... async with aiohttp.get('https://github.com') as r: + ... await r.text() + + + """ + loop = _get_running_loop() + return Timeout(deadline, loop) + + +class _State(enum.Enum): + INIT = "INIT" + ENTER = "ENTER" + TIMEOUT = "TIMEOUT" + EXIT = "EXIT" + + +@final +class Timeout: + # Internal class, please don't instantiate it directly + # Use timeout() and timeout_at() public factories instead. + # + # Implementation note: `async with timeout()` is preferred + # over `with timeout()`. + # While technically the Timeout class implementation + # doesn't need to be async at all, + # the `async with` statement explicitly points that + # the context manager should be used from async function context. + # + # This design allows to avoid many silly misusages. + # + # TimeoutError is raised immadiatelly when scheduled + # if the deadline is passed. + # The purpose is to time out as sson as possible + # without waiting for the next await expression. + + __slots__ = ("_deadline", "_loop", "_state", "_timeout_handler") + + def __init__( + self, deadline: Optional[float], loop: asyncio.AbstractEventLoop + ) -> None: + self._loop = loop + self._state = _State.INIT + + self._timeout_handler = None # type: Optional[asyncio.Handle] + if deadline is None: + self._deadline = None # type: Optional[float] + else: + self.update(deadline) + + def __enter__(self) -> "Timeout": + warnings.warn( + "with timeout() is deprecated, use async with timeout() instead", + DeprecationWarning, + stacklevel=2, + ) + self._do_enter() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + self._do_exit(exc_type) + return None + + async def __aenter__(self) -> "Timeout": + self._do_enter() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + self._do_exit(exc_type) + return None + + @property + def expired(self) -> bool: + """Is timeout expired during execution?""" + return self._state == _State.TIMEOUT + + @property + def deadline(self) -> Optional[float]: + return self._deadline + + def reject(self) -> None: + """Reject scheduled timeout if any.""" + # cancel is maybe better name but + # task.cancel() raises CancelledError in asyncio world. + if self._state not in (_State.INIT, _State.ENTER): + raise RuntimeError(f"invalid state {self._state.value}") + self._reject() + + def _reject(self) -> None: + if self._timeout_handler is not None: + self._timeout_handler.cancel() + self._timeout_handler = None + + def shift(self, delay: float) -> None: + """Advance timeout on delay seconds. + + The delay can be negative. + + Raise RuntimeError if shift is called when deadline is not scheduled + """ + deadline = self._deadline + if deadline is None: + raise RuntimeError("cannot shift timeout if deadline is not scheduled") + self.update(deadline + delay) + + def update(self, deadline: float) -> None: + """Set deadline to absolute value. + + deadline argument points on the time in the same clock system + as loop.time(). + + If new deadline is in the past the timeout is raised immediatelly. + + Please note: it is not POSIX time but a time with + undefined starting base, e.g. the time of the system power on. + """ + if self._state == _State.EXIT: + raise RuntimeError("cannot reschedule after exit from context manager") + if self._state == _State.TIMEOUT: + raise RuntimeError("cannot reschedule expired timeout") + if self._timeout_handler is not None: + self._timeout_handler.cancel() + self._deadline = deadline + if self._state != _State.INIT: + self._reschedule() + + def _reschedule(self) -> None: + assert self._state == _State.ENTER + deadline = self._deadline + if deadline is None: + return + + now = self._loop.time() + if self._timeout_handler is not None: + self._timeout_handler.cancel() + + task = _current_task(self._loop) + if deadline <= now: + self._timeout_handler = self._loop.call_soon(self._on_timeout, task) + else: + self._timeout_handler = self._loop.call_at(deadline, self._on_timeout, task) + + def _do_enter(self) -> None: + if self._state != _State.INIT: + raise RuntimeError(f"invalid state {self._state.value}") + self._state = _State.ENTER + self._reschedule() + + def _do_exit(self, exc_type: Optional[Type[BaseException]]) -> None: + if exc_type is asyncio.CancelledError and self._state == _State.TIMEOUT: + self._timeout_handler = None + raise asyncio.TimeoutError + # timeout has not expired + self._state = _State.EXIT + self._reject() + return None + + def _on_timeout(self, task: "asyncio.Task[None]") -> None: + task.cancel() + self._state = _State.TIMEOUT + # drop the reference early + self._timeout_handler = None + + +if sys.version_info >= (3, 7): + + def _current_task(loop: asyncio.AbstractEventLoop) -> "Optional[asyncio.Task[Any]]": + return asyncio.current_task(loop=loop) + +else: + + def _current_task(loop: asyncio.AbstractEventLoop) -> "Optional[asyncio.Task[Any]]": + return asyncio.Task.current_task(loop=loop) + + +if sys.version_info >= (3, 7): + + def _get_running_loop() -> asyncio.AbstractEventLoop: + return asyncio.get_running_loop() + +else: + + def _get_running_loop() -> asyncio.AbstractEventLoop: + loop = asyncio.get_event_loop() + if not loop.is_running(): + raise RuntimeError("no running event loop") + return loop diff --git a/lib/async_timeout/__pycache__/__init__.cpython-39.pyc b/lib/async_timeout/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3271822aa9719650e72765b8380f30c250dda834 GIT binary patch literal 6854 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", + DeprecationWarning, + ) + +__version__ = "22.2.0" +__version_info__ = VersionInfo._from_version_string(__version__) + +__title__ = "attrs" +__description__ = "Classes Without Boilerplate" +__url__ = "https://www.attrs.org/" +__uri__ = __url__ +__doc__ = __description__ + " <" + __uri__ + ">" + +__author__ = "Hynek Schlawack" +__email__ = "hs@ox.cx" + +__license__ = "MIT" +__copyright__ = "Copyright (c) 2015 Hynek Schlawack" + + +s = attributes = attrs +ib = attr = attrib +dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + + +class AttrsInstance: + pass + + +__all__ = [ + "Attribute", + "AttrsInstance", + "Factory", + "NOTHING", + "asdict", + "assoc", + "astuple", + "attr", + "attrib", + "attributes", + "attrs", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "field", + "fields", + "fields_dict", + "filters", + "frozen", + "get_run_validators", + "has", + "ib", + "make_class", + "mutable", + "resolve_types", + "s", + "set_run_validators", + "setters", + "validate", + "validators", +] diff --git a/lib/attr/__init__.pyi b/lib/attr/__init__.pyi new file mode 100644 index 0000000..42a2ee2 --- /dev/null +++ b/lib/attr/__init__.pyi @@ -0,0 +1,509 @@ +import enum +import sys + +from typing import ( + Any, + Callable, + Dict, + Generic, + List, + Mapping, + Optional, + Protocol, + Sequence, + Tuple, + Type, + TypeVar, + Union, + overload, +) + +# `import X as X` is required to make these public +from . import converters as converters +from . import exceptions as exceptions +from . import filters as filters +from . import setters as setters +from . import validators as validators +from ._cmp import cmp_using as cmp_using +from ._typing_compat import AttrsInstance_ +from ._version_info import VersionInfo + +if sys.version_info >= (3, 10): + from typing import TypeGuard +else: + from typing_extensions import TypeGuard + +__version__: str +__version_info__: VersionInfo +__title__: str +__description__: str +__url__: str +__uri__: str +__author__: str +__email__: str +__license__: str +__copyright__: str + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_EqOrderType = Union[bool, Callable[[Any], Any]] +_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any] +_ConverterType = Callable[[Any], Any] +_FilterType = Callable[["Attribute[_T]", _T], bool] +_ReprType = Callable[[Any], str] +_ReprArgType = Union[bool, _ReprType] +_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any] +_OnSetAttrArgType = Union[ + _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType +] +_FieldTransformer = Callable[ + [type, List["Attribute[Any]"]], List["Attribute[Any]"] +] +# FIXME: in reality, if multiple validators are passed they must be in a list +# or tuple, but those are invariant and so would prevent subtypes of +# _ValidatorType from working when passed in a list or tuple. +_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] + +# We subclass this here to keep the protocol's qualified name clean. +class AttrsInstance(AttrsInstance_, Protocol): + pass + +# _make -- + +class _Nothing(enum.Enum): + NOTHING = enum.auto() + +NOTHING = _Nothing.NOTHING + +# NOTE: Factory lies about its return type to make this possible: +# `x: List[int] # = Factory(list)` +# Work around mypy issue #4554 in the common case by using an overload. +if sys.version_info >= (3, 8): + from typing import Literal + @overload + def Factory(factory: Callable[[], _T]) -> _T: ... + @overload + def Factory( + factory: Callable[[Any], _T], + takes_self: Literal[True], + ) -> _T: ... + @overload + def Factory( + factory: Callable[[], _T], + takes_self: Literal[False], + ) -> _T: ... + +else: + @overload + def Factory(factory: Callable[[], _T]) -> _T: ... + @overload + def Factory( + factory: Union[Callable[[Any], _T], Callable[[], _T]], + takes_self: bool = ..., + ) -> _T: ... + +# Static type inference support via __dataclass_transform__ implemented as per: +# https://github.com/microsoft/pyright/blob/1.1.135/specs/dataclass_transforms.md +# This annotation must be applied to all overloads of "define" and "attrs" +# +# NOTE: This is a typing construct and does not exist at runtime. Extensions +# wrapping attrs decorators should declare a separate __dataclass_transform__ +# signature in the extension module using the specification linked above to +# provide pyright support. +def __dataclass_transform__( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()), +) -> Callable[[_T], _T]: ... + +class Attribute(Generic[_T]): + name: str + default: Optional[_T] + validator: Optional[_ValidatorType[_T]] + repr: _ReprArgType + cmp: _EqOrderType + eq: _EqOrderType + order: _EqOrderType + hash: Optional[bool] + init: bool + converter: Optional[_ConverterType] + metadata: Dict[Any, Any] + type: Optional[Type[_T]] + kw_only: bool + on_setattr: _OnSetAttrType + alias: Optional[str] + + def evolve(self, **changes: Any) -> "Attribute[Any]": ... + +# NOTE: We had several choices for the annotation to use for type arg: +# 1) Type[_T] +# - Pros: Handles simple cases correctly +# - Cons: Might produce less informative errors in the case of conflicting +# TypeVars e.g. `attr.ib(default='bad', type=int)` +# 2) Callable[..., _T] +# - Pros: Better error messages than #1 for conflicting TypeVars +# - Cons: Terrible error messages for validator checks. +# e.g. attr.ib(type=int, validator=validate_str) +# -> error: Cannot infer function type argument +# 3) type (and do all of the work in the mypy plugin) +# - Pros: Simple here, and we could customize the plugin with our own errors. +# - Cons: Would need to write mypy plugin code to handle all the cases. +# We chose option #1. + +# `attr` lies about its return type to make the following possible: +# attr() -> Any +# attr(8) -> int +# attr(validator=) -> Whatever the callable expects. +# This makes this type of assignments possible: +# x: int = attr(8) +# +# This form catches explicit None or no default but with no other arguments +# returns Any. +@overload +def attrib( + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def attrib( + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: Optional[Type[_T]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def attrib( + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: Optional[Type[_T]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def attrib( + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: object = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., +) -> Any: ... +@overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + alias: Optional[str] = ..., +) -> Any: ... +@overload +@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) +def attrs( + maybe_cls: _C, + these: Optional[Dict[str, Any]] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., + unsafe_hash: Optional[bool] = ..., +) -> _C: ... +@overload +@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) +def attrs( + maybe_cls: None = ..., + these: Optional[Dict[str, Any]] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., + unsafe_hash: Optional[bool] = ..., +) -> Callable[[_C], _C]: ... +@overload +@__dataclass_transform__(field_descriptors=(attrib, field)) +def define( + maybe_cls: _C, + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + unsafe_hash: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@__dataclass_transform__(field_descriptors=(attrib, field)) +def define( + maybe_cls: None = ..., + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + unsafe_hash: Optional[bool] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... + +mutable = define +frozen = define # they differ only in their defaults + +def fields(cls: Type[AttrsInstance]) -> Any: ... +def fields_dict(cls: Type[AttrsInstance]) -> Dict[str, Attribute[Any]]: ... +def validate(inst: AttrsInstance) -> None: ... +def resolve_types( + cls: _C, + globalns: Optional[Dict[str, Any]] = ..., + localns: Optional[Dict[str, Any]] = ..., + attribs: Optional[List[Attribute[Any]]] = ..., +) -> _C: ... + +# TODO: add support for returning a proper attrs class from the mypy plugin +# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', +# [attr.ib()])` is valid +def make_class( + name: str, + attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], + bases: Tuple[type, ...] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + collect_by_mro: bool = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> type: ... + +# _funcs -- + +# TODO: add support for returning TypedDict from the mypy plugin +# FIXME: asdict/astuple do not honor their factory args. Waiting on one of +# these: +# https://github.com/python/mypy/issues/4236 +# https://github.com/python/typing/issues/253 +# XXX: remember to fix attrs.asdict/astuple too! +def asdict( + inst: AttrsInstance, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + dict_factory: Type[Mapping[Any, Any]] = ..., + retain_collection_types: bool = ..., + value_serializer: Optional[ + Callable[[type, Attribute[Any], Any], Any] + ] = ..., + tuple_keys: Optional[bool] = ..., +) -> Dict[str, Any]: ... + +# TODO: add support for returning NamedTuple from the mypy plugin +def astuple( + inst: AttrsInstance, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + tuple_factory: Type[Sequence[Any]] = ..., + retain_collection_types: bool = ..., +) -> Tuple[Any, ...]: ... +def has(cls: type) -> TypeGuard[Type[AttrsInstance]]: ... +def assoc(inst: _T, **changes: Any) -> _T: ... +def evolve(inst: _T, **changes: Any) -> _T: ... + +# _config -- + +def set_run_validators(run: bool) -> None: ... +def get_run_validators() -> bool: ... + +# aliases -- + +s = attributes = attrs +ib = attr = attrib +dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;) diff --git a/lib/attr/__pycache__/__init__.cpython-39.pyc b/lib/attr/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42f272ee1c31c80787683d8887669d2a83ad9ab8 GIT binary patch literal 2025 zcmZ8hOLNpl5SFy-eXrkd8yFrjEZDoo1OmjNAW#9Pf|Cj)RHn9SO4f{b6>B7AX5`m3 zx#kz-LUPL?e6g0OZ?_u`JkS1G;J;NJ=O1L=f6Bl- zgimj~j>8@9Mh-1jm%6}vkta*E%TZa5&=Fao60;hIYVdi^I9}3=jfcAr}J`wF33f?D3|Dx zT&BzN96cw`)ARBIy^zb+BO))-i}Dh^Brns;@(R6@?>C~42FthdtEsO{=tn1zWT!J?8$lB93uT1TwjmA!k(w}xwXN=kQNCH#!W5_beiU-w zBuayxdLUDl=`bF2JUclMhN&!OXCihI?tQS=so09Ic^DWw;%l7*w(4t>rID}|v7bcy z!j^`0^)3b51E0CY{k1t z$2%!O)=r>$o#Vg1&EgosC%IXY#N?YJGlb<<_YMg);UZNc@QvVPg&YVH#zw@Pn1pbZ zq(o4*-HqUjk{R-O5=KI$5$HaieXIK5;GmnU=_YE>JNC%t8~4HS*t4Tp?2#A2 zF!B%lVDGp#)DM%xZg6Nz&$nJ4UwWLRM=Bf)4Y?L{$j183+vI~QFFP)6`I$)=st>KdrZ;mwzi@Atpc@WcBr0u`qHCqnO;B<&AU!}QSSLiLT%W~2XAxYO665o;*~ z>JNnOqr*MM!ZH*Z;obM1dg~sqqO(RSqU@WSQFbZJHs7uWt*(mY= z^{(?Dr0OVzlTs}qj3JC5EF;Vz%p+6~&LQB?RpS8G3wP84a_12yaLd)Ii40b>T12P; zbUw2seFU9y;43U~ZR@*P4BeJQT46Z$)Q8-1ZYo!RcWs%$WXS!rRR+UlHyji`pdnge zs7ZeiauEkQS-3UAuwRShw%*^eWsKHNJ~{1M5PemIZ=3AhWk#nN3<>#eLqna$13P{ug8HG#>WrDt zvRT;}v^p*$}fAC zrq(boHxO14u48C*7}S0~@A6_pi+%)(&WgRDIw)M*EPCSsPB*;?P;+Zu%UgC^?zl_b j)#kk0DmC1)>y^BJ%hj^m@XEm9JB}27CAb4pqvihqds#W+ literal 0 HcmV?d00001 diff --git a/lib/attr/__pycache__/_cmp.cpython-39.pyc b/lib/attr/__pycache__/_cmp.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6889c3e60e4be6ba41881f75817208b731f274aa GIT binary patch literal 3756 zcmb_e&1)pb6|d^)ndzC8X0;#QjUC679mr;cJn?P<4niveyAd%21-x8?K8VQYCm+V5EL%@eX0&!sXxQATx$Mh+X)1D21m=Ndps%P}E&N>*UoBFPL^*(;@ zy^0nWn+)I2{_?8)_hrWZPJ{Db9fO~sm~B*&NuIFnvgO-cda{yuqsn%LGwI8!tjW3z zWaHE7wl8nU1-bZ`Z&zhYUVhBBYjR0mL0gwsVsXx69R`rUT5@YF%!B^E^$-T>`DZ57RX*=QW;Xg5(k;(Gp( zd;v-Y#oPdW$p_rBM{K|>B>2?-Et8d#y7W(ioXP5h>vdT>39KjUIeX-N%w({}Y~>L{ zud&8*?)L2Db=iEEoz$`KQqJAJ1+02*b$V`fG56eR3#%2k`p0vtm-C8SUBW6fh}-{q zZuLs;yVa}Uz<*NBYqr+sG5Zbcb9qf(e;hpZPa1hu|1EFq)t>~m@sz_7mAvjl+rGY_hb|bf>n4N@!&!iH+02jFF=_NyIQZB-Z*9^o_D2 z>kD@!Z5t^&%%l-<-_ZcuiHZ#Nq>A=+yB{TX1#>H6fOeoxh^VeFw$2dknxA9lrr^a9D$VU$cred=HmC9yqrtdLs4fj!DZ zw!5c#)^yzb%gHD9tH~b?A*|A8)JaB1TEqr2mWE3qUq<_8Yyb0B_5Zmw)1~~Rk*~+W z;2X(3aM0C+*{>`5;2Vj4;o`57{=C_Dvn&D14<@lzp@~K+w8vwx@vEUqMR!Y26#Qt8 zBZt%%p?+$9=NnqtiN?vz%)U1oCyLxn$u9h1jyeZ%Ai{wWE=HFMy7VP*H&#C^y)R6$ zbyaNA0U+6hhK{(e`VoB8`Sh)`2z+Zs?txP9kyGF9iat~`%HU>F*(grn1x|uaM;t0` z$i*alZ*^7Nx!t+ldB-glRdOlS_KQXsnk2I(43QziIE^iOi{VUzF1Qp|!q|l6mE@

QcdRn@&d$%{-5nEK_4YXG?MDM;b}0J(AVh5KjE{?8Er~|EGFpFt z%RfV5({E!boEXH#&RUWoP?>df%3aS9>e=+ZIuIiSVhZe1^K_JMG1yS!Qdphh75VqE$|0W2GbLUz2KzD#6SYjZ__l4n0M!j8_^D52!U7 zk5wv*rLZ?tz5O$HD4M?{^Z!)qOc#y*B<)$ajVW3-vr!VxnQJ$leVr+t37zSj$+oY2 z$;XPOW5I!X`&v2~m2^T!9xyc9mxqxdq|9eZ{zLsbnANXQ^&MinajNA-49x~A_C25b z+~eNAY5{s)fKiJ#(FVNrUuvIU{lc&Me4+9huT^mF3*T>g`fX6^5xMzCAS1NiLoo}e zB;GHN*$Kdg{9T#8>AYo@yyxyQ%ZX*&@6qx3M|~=Um=d!*XCa7!m^ZzB8d=_hSz}}} z|Ig0I2EIX%ME(ue51I#sX)%FVCsHE=(rMjHXNL{*xYD5=0PW6~|W z7*y%di-g|9+>i(nl36vVX_~npAfawv%sUvkcN6I>IKj&`XMe(5>CctigV4T(@UPNL z)y;Uh2Q}0#>U56hm+N$Xor?o2xRT;P&YyDAve4_t81H#G-@}ve1Xox-Vfy>DLf7e= zIMA-lTBPW(O3;o4{cU`18H#8yB7P zOzq{N!VAM`~`=ZL@byfmx$eCL4n6~+7*hyloSw5m%>sv@EkW~ih3Bz ztQUsZ=F=wqLolO%M3qCs9qKtqyoKH&io+nQE;L(S3m%|p{!8{GYdic4bf8RjpFO-z zs?4G0N1mzS{R?#-)Fj!jGW~Js6BJRz?8PLyPJPIVX7=o)JV61kOR(*hh~C%&@F+0B zAf$9n04jxW{GUOZBHr&%4CM{xH=*l*J0o7;o&Zn9aZhIe1GWd$Q9U6K=F|5bw=>;S z`lrmjOk6Fy?7!?dM;{54UX$k&_=?y-h{rs=_KsqMj80zJ_8fF5b(Rq?#LSsMFUWEW zeEk7s=AN3y?fNEtTlx|Wn!1ffe-~9zbx3m2UB64S6tbKjI^#JVI1=Za_f&64S))u& e617e?HE*Cofbp!pxZMd{N3 literal 0 HcmV?d00001 diff --git a/lib/attr/__pycache__/_compat.cpython-39.pyc b/lib/attr/__pycache__/_compat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b50ce1de629c9369a857c0978eb6ffab7c5da1d GIT binary patch literal 4117 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 Yx-#YS`me3BYcCbQa{WK*FRU*A2O=mtJ^%m! literal 0 HcmV?d00001 diff --git a/lib/attr/__pycache__/_version_info.cpython-39.pyc b/lib/attr/__pycache__/_version_info.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90a1f1b7ec4966bf38a9816873238822b8a0b476 GIT binary patch literal 2303 zcmb_d-EUhp6t{gpa+{`Qbk#%=AZrX*sy3_zA+6e~p|QcFsY7ghMUgBwcG|n%kIZ%| zHA-LTc-&vW%l6m@_=Eh)Q~w2C;MliKldU`;;TxZOu6=xbj(@-7@VU7-$fdp(XN}jr8v!`#ywRIWzltBCtCm?ccAMI2#MNQpf(Y>9mr5dg1o@(c&ydN zS_0aPpRLPT5s2_Pg2)>#tLLaaCu$;r_q>=9b$GueW+9`7o&7;7otDLeqF3_YAdxzS z_9fk~hH1I|RCWzDyWG$&FX*ARMm|eQIy88J>-TT37lxOL(}vysPHxe`%s z;KT(F?cr0=qbSV6b+DC1km8dImCvH0G=bFubS`|EU<~(c!&bDht&pviuIj>VB}_D z4FD&C%H~&qZ-jdU;0uR;j1jq4=z#+KO0z4~GEFsLH!fdJ_j#6z(=u*V73@I&&cp0% z`=!?Dc6LCG;)U*&Du*lJ%$s9GIRJiS`x1;NeUAfxeW~WTJi9Bv>U8-zZ7Pbm!`W)a zc80NT#|bhkKJD0D$Ro&F5O%fMv8k%l}cR!W4MGv2&V^ku1nZ+ zq!RBtS947I^I+c1XE3DaL7=386H><^c^CR{X$sfL6;pt^;NZ`w0(2de;lc>L1Q1@~ z4ml);!H=l85+Z{Su4CjLLt^wZdW>$M5g7%00M#+xIu4X$m--NdouAI@I8@i%+(&MX z40Jn$r1IC3iW!s77`qQf+W$>rf)_kXhYM4gc(eaC1xQAH;v&56#Ih5vDkn}Hg3AnJ z?tghYxbXGhyjk;-O&BYHFO1nbV|ghCnbRALJsa@sbf(6bD7!$A$nExy6K^}Q;KXGR zHh{uai%y&MWXh%4Ert5P8Tj*FaoVaA=Mj6&5k<{{aA&EVizEma>)1a~`1M9|IZ3?5 zRx`0Py+P46WtnN^D!d&tH=rx(Ml9!hvWhZ>S-;CcKH D_&hHT literal 0 HcmV?d00001 diff --git a/lib/attr/__pycache__/converters.cpython-39.pyc b/lib/attr/__pycache__/converters.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30f54136b08aedc06bb00758f98330c329b13dd6 GIT binary patch literal 3509 zcma)9TW{RP6`mov7p>Qqszwl}NiYxsG%MJQq&97_a19%3oCI*?08(xX0VK5?YH4#x zVrEwE3hS5JK%L*v7I2cMJme4YHBb2qd1=2Hl6!L$gu)D$GiT16Ip=)e90ivzcL<)3 zzP}M}ULoWM{jvPm__&KEKSLLi0SRenC3HYTJ9H9jY!7To@Vk*X9!(N9 z%+fP16c^I%{D`f|!H*Puj~3Uxduf`fK*d@5!9SD;hAI<1yJ#IeJo@P4gAa?=eKRyI zoIK8X&nd2kJPIa>^5V!#vy>O@EZ3`pL^PqZXsOIQ$+Bdqcc`BRe(1yNCO<*1cwpw7YRHK3MWz@xz;h5~nC?1a3 z5K6F4a-}u+{sE?Z-|ZO5qiN2uOYKj|p2SI_#p5*MLhz79A{)adXcIx6V_+Vc-^-gU zj##XWrct2HomP7Lc_4x@_D+)EB;kuieWPoxqdYC_McA=dCKv*At2WXo*8VvQk_ks9 zm;u*iNDZk27=}FT_u1~B-CcLbOs>RG$J0Xlt&ZV!bTg#zgotiP4iUi_Vt7ctqX*xi zD{O3n*T&p7aS1Dada&O={#0VWp0^Zo&l_CT3rk&QO|K!Y zLZSGL)@lP|1yzM5l<|lFO%QQScWrd9p~+2jHySF4JZSuGmO3< zN(bci8K9-#3IKaX|4Gd_6+31eZ;ab|y!yVYtQmbqzaq+pr1i`a(F^j(5nrfA=x8|u z)<)O_Sgo{czFOtiOfyb9_z%o733CzjfEB^>BpC8JW~ww~CsVdvQOY($$^sommEaOA z!?K8NMpgy-?Sf=)c?W}OGx)c5KAtZrlSvp$V8ug3@>h{Jce_{ z_V%j7=k8tdRcZVMdQLJmT2T_D;UY*Z5WIHziW(E9g*{7AxWN5o+(4pgR3CrY^?yAQ zlLhzw43GWA-K%));QZacPoy$tvC<37ukr^06wI#zOx;3$ptX z(+h^Q&iB?N7eu)>u~2cb<$7;(EzB*itS(K{_KF+2)3-rMd%wf40By-8P_ToMVq>1c zLKhH4CkB2?;0`(q3>KY5YV~aK21Lc{y3?x5oy?wwWm_2g6Pje`$mTWLwysf&TK{vL zHhy<#v--Ny?ly&%UOBDiT20jZXmW&3;k=51GdE}CrG*lOj8N5mOP^A_ExfIBHbaSW zM#SFdsO=6RKeO<47hldH`JAMi4w!d(M2Y_UlBCuSQBK&Hk$;o_8vYKOVA)n+)hxST zmUC=N*I7JBx0pm(lb%ZUE_YAen`N27(=*)d0XVIv$x-kCwMe<3zWj~fBpag^(ZmR; z{xA!9-#5_emvnM#WzZmTKD4EP!7ZP?SIcavEq_g9%H@j0^c9ILePw8OePq`!t+~+T z`?%b>zix9mx@J+NMOSQ!*0jP;uEEos$Kf9u+n`ThLvhi-y9{ktnPzHS_liE6oZT6%BLNg6D}%U^bYR zC|iJf10s*mq{bn6ql02h|5<2Fv<~fBO)9ihuUoV{FU*FXCv;^wXn|+Od7w-cRy4dZ z?pi|!h6AeqF;H_Q307z*8ju=YOIFNHDcZNkSvX1f9aARha?3$w)uq?%cDv24{PF+a CK(=oH literal 0 HcmV?d00001 diff --git a/lib/attr/__pycache__/exceptions.cpython-39.pyc b/lib/attr/__pycache__/exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b8a15629141d51f7a27d39990e34c37f8dfd9ff GIT binary patch literal 3137 zcmbtW%WfP+6z!hrdD;`(aR@PCQGx{-p-f&9fheF@P6Cl46X7I_VNkoKtHy59(>{_=T`T;5<6h%QP=dT_qpfZy2V?qri15;-`B+VHOKjb z!0h7z+`_AWLGigG9O3?iS2{1sT^G0{%D`pm0+)b2Q30-4Tn4U+8gNZ|Xs-a*MFY5D zaTT~J7JwII4efQ{MbQFoS=<0VC6<7fEN%iXixuD%xq$wQ;`El&UJbCbxyJ>%g;&3W zLONYnINg$PyJb=8deYl*yA@H!NH1HstCD>g-%%=2`+qhWo^>{`b(g~7Fg3g%NfyT1 z@HmhxQH&d-!hUKb+vU$B>q{9^og9vg6wD;3vC|9_5e`BrI!z1j88!@Q70t{p4a4XV z(he(UWFA*L`p{8z*xMF=-QT#;d!(^;Z!1vYsCQfHCng#7c1Fh6 zyIe@sGg6zYz58Lmr$Zwzj(G3{^mLCbd`W&6$dSRub!W7vNKkS_T}4rMy{6~dy5p5! zM#p*Op?5i3&Go)l2%78Oo6~)&_Ab{)>dRiy_H`>?`DkM0%|lO>uo+dRf=H+e6%$kC zUk$3GNL?=5lBRfLTHf20XYu~k~9+JS*#LX@@HpEi6DdYMH z!gr9UVy9R%Fo1{ zP$%qPXhUZ@21EVo`OB2V06W+8AQdu56gO`Y`1&+xkN4*#5WY5J0Kz9Wz}lmj$8lmf za>m4~a~u3n*dW*Sd1!V?#MC4{Id|CCpFUKn1b32rqVU)t)IUjao-?yI8m9`o5TfCD z>92RL{j+P+k$Q86IpGsCUwN>H9mWrn931`oEl;)CK^B^uD z{A9@SrubxHieSuz_|P|{EBe>(p)ihd51bdJUrPt>LFu=W>)_w<%LgTto^ZCDLwB=X z$tpS>NtHDo!UHWax=@61XnbE${?00Pr^v>hmeIi0skhN^=w=Pyw~mjlHksJ8couDs z@!IU~xToE!yTA6T^*ZA<0=g`WANr zs+lZCt7o|Bpoi~&4PXSF1h#@8m^ z@nxsBSW&jF-O5hQpBDBKWUmQ|@@m$6Og>;I-aw|uhQ6r!=j!JEJd-a$eq<& fRM7M@yeFp5>NoTI-)HvrMWdFNmg~i@v5eo}doAaG literal 0 HcmV?d00001 diff --git a/lib/attr/__pycache__/filters.cpython-39.pyc b/lib/attr/__pycache__/filters.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab4cb5fce418ab71a69dcfc5d40fe6ac6fa53c8a GIT binary patch literal 1604 zcmbtTO>fgc5Z!f>=A$13D1rhidw@f#Vk(YNRe@S0Dxn^z0I4#vUGJtfbz-x-DQ%RV zTB+>;@jG(h5BbW8GdB*1nRT27BoLx2&3I=uo_#y-jXynIBe0%-p5rq`LcU-yzAOly zfT=}roNN(JxfRj2wPjJl3%qzjwhFw&C*WJ;WnO`AiB~sCqt;ujt;X$k+=&iZSBX|P zVy!UJLMqmZCG&i(rR^&o2HLZ0UnyMBplS6f9EaOoEdo5r7w!QcSp`$~!D)l!g!IW8 zIi|-H;sUoW;+6#%ioLZOgY{DAuIwo0Gu=%h!QvM4S~Bj5juP5a7zdHBlz@~DXNb%| zP`3H~7bMsbM555{g6osFvqx}$pK5w+Nvls^6H4A&oNST--Dp^8EmUDgX}=SQw5Sgg zF|bn0UP=o=qy|J*p~(03)u!`GL35nVK!%C4Ce)se6K5yUuG;mvkd78gKXzV(+l~sg zc$oOXp1&iMgK!@WTWcqWX?1x=bi_d-SFS+iZ(yW1@2VsUwfkY$*Y`~(X zzfcySe8ZC15{bQ$6X@_wnL~ZaNpN-COo2m|N8N{@h@gJ^Bb^)rj3pG7C8av8|B(2s z0Krslx`8=wluI;ZF4l$_F*g}Obd9ML`nT@-pB4YBK;AnJ0#77)RLT{DNx z#Fu`X;Zc*l0nc>I!Ynh>LlZe1CU{Y(v}Y8Br?4AJTiGKedk#~>+7gqR%!B?%{&pn1 zOkntceBmDLI+(f!u20{QGrCWbdY_z;eUO!D`T*MKx<Gg#;5sMrnMAvtMzm^%wW##D96nkmRR zh%d?1C)1&42mi{H$TI)NR6gr}peonFCAgw-#ubRoa3ziSkxXftX2!#Gt}?{yE@A6( zF7ii=xX82QElA6mxQ+EpsWB#onGdj>nVF1Q8ffZngjb literal 0 HcmV?d00001 diff --git a/lib/attr/__pycache__/setters.cpython-39.pyc b/lib/attr/__pycache__/setters.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89b004e0c0d2f2e129f805dd74c3590f43d62eb9 GIT binary patch literal 1509 zcma)6&u`l{6ecBEmKCQB(xOF&Vc=ybY&7bRTZ5u#kv7-p*VI6ck0C+j6L4@K;jPfc^7)02fPP;mxp`<`atv+Bo2B1 zkPJ5GKZbj9I+bPdj@4Rlb}Z$oW)rEHEE6qEYK)4)&lF+~XxmLPSx)k!kF>qMujE{m zPhmYD*G4>7N~#RDtr`M$!Mb323*4V%LJ{&QB7PVk&$DshBUNs7w(z+I%r%#Ne@1fL8lG!Qk%5QMC&6d`MB+s z;!R>PwT_y#{t`WK(>>2aVVHk@-rF7hssY>RFjILodM5O#k=5v^GKoG;xlkh`w0SUk zk&j0@H)5wsv(xlQ=n)3Gdwy6{y=(h#R9aPnCskevi$8z}gnCXuuQ>DXa5{Qa$Sf`N z=Rpfo>CA>*2q+sy%R|+Yi)L=+~G-7EuX%U>enG$b$R_B0#f!orgb&@1@lO$O}wzw^1e_|zw zCDbT3;;U`&YmhM+Gz<9-3=BB|z%`K2IEPT`6DWS;>hJBBYm4tJTneJFN&UVO=x~|N z@U~`9^PH!~UJ^E3sbI+R3Cj&TPW5Glxj%3fLXWQ|sxDDX%Y${?Ta5>v@?nd@d!(@5 zEuDQ$MR2}b+=g|H^(B4?vhb+~zVE2(F!MiRFtR1)1`L1-f>fyO#%-Jh)YMWy=kLEo z0lWin-Qcpaot^cxaRSm`<7NFi+w+vrbzv?e1$cLQt8m{SBm0_+?)mPAkd?LDTR zj=Bjm2ZIfG#}Y2HBx#9dzYWFPOjHIQ53NuwR{B;&x+p$V}Whc$vI>bs^|~ biVPls#_K{nM)BG*c%44&yTNwQ1Kau+iYj1m literal 0 HcmV?d00001 diff --git a/lib/attr/__pycache__/validators.cpython-39.pyc b/lib/attr/__pycache__/validators.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..daf8b8d4d4a2e0351ff5ea12cb2798831a4d5d38 GIT binary patch literal 20165 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 EFUA?b#sB~S literal 0 HcmV?d00001 diff --git a/lib/attr/_cmp.py b/lib/attr/_cmp.py new file mode 100644 index 0000000..ad1e18c --- /dev/null +++ b/lib/attr/_cmp.py @@ -0,0 +1,155 @@ +# SPDX-License-Identifier: MIT + + +import functools +import types + +from ._make import _make_ne + + +_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} + + +def cmp_using( + eq=None, + lt=None, + le=None, + gt=None, + ge=None, + require_same_type=True, + class_name="Comparable", +): + """ + Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and + ``cmp`` arguments to customize field comparison. + + The resulting class will have a full set of ordering methods if + at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided. + + :param Optional[callable] eq: `callable` used to evaluate equality + of two objects. + :param Optional[callable] lt: `callable` used to evaluate whether + one object is less than another object. + :param Optional[callable] le: `callable` used to evaluate whether + one object is less than or equal to another object. + :param Optional[callable] gt: `callable` used to evaluate whether + one object is greater than another object. + :param Optional[callable] ge: `callable` used to evaluate whether + one object is greater than or equal to another object. + + :param bool require_same_type: When `True`, equality and ordering methods + will return `NotImplemented` if objects are not of the same type. + + :param Optional[str] class_name: Name of class. Defaults to 'Comparable'. + + See `comparison` for more details. + + .. versionadded:: 21.1.0 + """ + + body = { + "__slots__": ["value"], + "__init__": _make_init(), + "_requirements": [], + "_is_comparable_to": _is_comparable_to, + } + + # Add operations. + num_order_functions = 0 + has_eq_function = False + + if eq is not None: + has_eq_function = True + body["__eq__"] = _make_operator("eq", eq) + body["__ne__"] = _make_ne() + + if lt is not None: + num_order_functions += 1 + body["__lt__"] = _make_operator("lt", lt) + + if le is not None: + num_order_functions += 1 + body["__le__"] = _make_operator("le", le) + + if gt is not None: + num_order_functions += 1 + body["__gt__"] = _make_operator("gt", gt) + + if ge is not None: + num_order_functions += 1 + body["__ge__"] = _make_operator("ge", ge) + + type_ = types.new_class( + class_name, (object,), {}, lambda ns: ns.update(body) + ) + + # Add same type requirement. + if require_same_type: + type_._requirements.append(_check_same_type) + + # Add total ordering if at least one operation was defined. + if 0 < num_order_functions < 4: + if not has_eq_function: + # functools.total_ordering requires __eq__ to be defined, + # so raise early error here to keep a nice stack. + raise ValueError( + "eq must be define is order to complete ordering from " + "lt, le, gt, ge." + ) + type_ = functools.total_ordering(type_) + + return type_ + + +def _make_init(): + """ + Create __init__ method. + """ + + def __init__(self, value): + """ + Initialize object with *value*. + """ + self.value = value + + return __init__ + + +def _make_operator(name, func): + """ + Create operator method. + """ + + def method(self, other): + if not self._is_comparable_to(other): + return NotImplemented + + result = func(self.value, other.value) + if result is NotImplemented: + return NotImplemented + + return result + + method.__name__ = f"__{name}__" + method.__doc__ = ( + f"Return a {_operation_names[name]} b. Computed by attrs." + ) + + return method + + +def _is_comparable_to(self, other): + """ + Check whether `other` is comparable to `self`. + """ + for func in self._requirements: + if not func(self, other): + return False + return True + + +def _check_same_type(self, other): + """ + Return True if *self* and *other* are of the same type, False otherwise. + """ + return other.value.__class__ is self.value.__class__ diff --git a/lib/attr/_cmp.pyi b/lib/attr/_cmp.pyi new file mode 100644 index 0000000..f3dcdc1 --- /dev/null +++ b/lib/attr/_cmp.pyi @@ -0,0 +1,13 @@ +from typing import Any, Callable, Optional, Type + +_CompareWithType = Callable[[Any, Any], bool] + +def cmp_using( + eq: Optional[_CompareWithType] = ..., + lt: Optional[_CompareWithType] = ..., + le: Optional[_CompareWithType] = ..., + gt: Optional[_CompareWithType] = ..., + ge: Optional[_CompareWithType] = ..., + require_same_type: bool = ..., + class_name: str = ..., +) -> Type: ... diff --git a/lib/attr/_compat.py b/lib/attr/_compat.py new file mode 100644 index 0000000..35a85a3 --- /dev/null +++ b/lib/attr/_compat.py @@ -0,0 +1,176 @@ +# SPDX-License-Identifier: MIT + + +import inspect +import platform +import sys +import threading +import types +import warnings + +from collections.abc import Mapping, Sequence # noqa + + +PYPY = platform.python_implementation() == "PyPy" +PY310 = sys.version_info[:2] >= (3, 10) +PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) + + +def just_warn(*args, **kw): + warnings.warn( + "Running interpreter doesn't sufficiently support code object " + "introspection. Some features like bare super() or accessing " + "__class__ will not work with slotted classes.", + RuntimeWarning, + stacklevel=2, + ) + + +class _AnnotationExtractor: + """ + Extract type annotations from a callable, returning None whenever there + is none. + """ + + __slots__ = ["sig"] + + def __init__(self, callable): + try: + self.sig = inspect.signature(callable) + except (ValueError, TypeError): # inspect failed + self.sig = None + + def get_first_param_type(self): + """ + Return the type annotation of the first argument if it's not empty. + """ + if not self.sig: + return None + + params = list(self.sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + return params[0].annotation + + return None + + def get_return_type(self): + """ + Return the return type if it's not empty. + """ + if ( + self.sig + and self.sig.return_annotation is not inspect.Signature.empty + ): + return self.sig.return_annotation + + return None + + +def make_set_closure_cell(): + """Return a function of two arguments (cell, value) which sets + the value stored in the closure cell `cell` to `value`. + """ + # pypy makes this easy. (It also supports the logic below, but + # why not do the easy/fast thing?) + if PYPY: + + def set_closure_cell(cell, value): + cell.__setstate__((value,)) + + return set_closure_cell + + # Otherwise gotta do it the hard way. + + # Create a function that will set its first cellvar to `value`. + def set_first_cellvar_to(value): + x = value + return + + # This function will be eliminated as dead code, but + # not before its reference to `x` forces `x` to be + # represented as a closure cell rather than a local. + def force_x_to_be_a_cell(): # pragma: no cover + return x + + try: + # Extract the code object and make sure our assumptions about + # the closure behavior are correct. + co = set_first_cellvar_to.__code__ + if co.co_cellvars != ("x",) or co.co_freevars != (): + raise AssertionError # pragma: no cover + + # Convert this code object to a code object that sets the + # function's first _freevar_ (not cellvar) to the argument. + if sys.version_info >= (3, 8): + + def set_closure_cell(cell, value): + cell.cell_contents = value + + else: + args = [co.co_argcount] + args.append(co.co_kwonlyargcount) + args.extend( + [ + co.co_nlocals, + co.co_stacksize, + co.co_flags, + co.co_code, + co.co_consts, + co.co_names, + co.co_varnames, + co.co_filename, + co.co_name, + co.co_firstlineno, + co.co_lnotab, + # These two arguments are reversed: + co.co_cellvars, + co.co_freevars, + ] + ) + set_first_freevar_code = types.CodeType(*args) + + def set_closure_cell(cell, value): + # Create a function using the set_first_freevar_code, + # whose first closure cell is `cell`. Calling it will + # change the value of that cell. + setter = types.FunctionType( + set_first_freevar_code, {}, "setter", (), (cell,) + ) + # And call it to set the cell. + setter(value) + + # Make sure it works on this interpreter: + def make_func_with_cell(): + x = None + + def func(): + return x # pragma: no cover + + return func + + cell = make_func_with_cell().__closure__[0] + set_closure_cell(cell, 100) + if cell.cell_contents != 100: + raise AssertionError # pragma: no cover + + except Exception: + return just_warn + else: + return set_closure_cell + + +set_closure_cell = make_set_closure_cell() + +# Thread-local global to track attrs instances which are already being repr'd. +# This is needed because there is no other (thread-safe) way to pass info +# about the instances that are already being repr'd through the call stack +# in order to ensure we don't perform infinite recursion. +# +# For instance, if an instance contains a dict which contains that instance, +# we need to know that we're already repr'ing the outside instance from within +# the dict's repr() call. +# +# This lives here rather than in _make.py so that the functions in _make.py +# don't have a direct reference to the thread-local in their globals dict. +# If they have such a reference, it breaks cloudpickle. +repr_context = threading.local() diff --git a/lib/attr/_config.py b/lib/attr/_config.py new file mode 100644 index 0000000..96d4200 --- /dev/null +++ b/lib/attr/_config.py @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: MIT + + +__all__ = ["set_run_validators", "get_run_validators"] + +_run_validators = True + + +def set_run_validators(run): + """ + Set whether or not validators are run. By default, they are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()` + instead. + """ + if not isinstance(run, bool): + raise TypeError("'run' must be bool.") + global _run_validators + _run_validators = run + + +def get_run_validators(): + """ + Return whether or not validators are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()` + instead. + """ + return _run_validators diff --git a/lib/attr/_funcs.py b/lib/attr/_funcs.py new file mode 100644 index 0000000..1f573c1 --- /dev/null +++ b/lib/attr/_funcs.py @@ -0,0 +1,418 @@ +# SPDX-License-Identifier: MIT + + +import copy + +from ._make import NOTHING, _obj_setattr, fields +from .exceptions import AttrsAttributeNotFoundError + + +def asdict( + inst, + recurse=True, + filter=None, + dict_factory=dict, + retain_collection_types=False, + value_serializer=None, +): + """ + Return the ``attrs`` attribute values of *inst* as a dict. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the `attrs.Attribute` as the first argument and the + value as the second argument. + :param callable dict_factory: A callable to produce dictionaries from. For + example, to produce ordered dictionaries instead of normal Python + dictionaries, pass in ``collections.OrderedDict``. + :param bool retain_collection_types: Do not convert to ``list`` when + encountering an attribute whose type is ``tuple`` or ``set``. Only + meaningful if ``recurse`` is ``True``. + :param Optional[callable] value_serializer: A hook that is called for every + attribute or dict key/value. It receives the current instance, field + and value and must return the (updated) value. The hook is run *after* + the optional *filter* has been applied. + + :rtype: return type of *dict_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.0.0 *dict_factory* + .. versionadded:: 16.1.0 *retain_collection_types* + .. versionadded:: 20.3.0 *value_serializer* + .. versionadded:: 21.3.0 If a dict has a collection for a key, it is + serialized as a tuple. + """ + attrs = fields(inst.__class__) + rv = dict_factory() + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + + if value_serializer is not None: + v = value_serializer(inst, a, v) + + if recurse is True: + if has(v.__class__): + rv[a.name] = asdict( + v, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain_collection_types is True else list + rv[a.name] = cf( + [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in v + ] + ) + elif isinstance(v, dict): + df = dict_factory + rv[a.name] = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in v.items() + ) + else: + rv[a.name] = v + else: + rv[a.name] = v + return rv + + +def _asdict_anything( + val, + is_key, + filter, + dict_factory, + retain_collection_types, + value_serializer, +): + """ + ``asdict`` only works on attrs instances, this works on anything. + """ + if getattr(val.__class__, "__attrs_attrs__", None) is not None: + # Attrs class. + rv = asdict( + val, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif isinstance(val, (tuple, list, set, frozenset)): + if retain_collection_types is True: + cf = val.__class__ + elif is_key: + cf = tuple + else: + cf = list + + rv = cf( + [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in val + ] + ) + elif isinstance(val, dict): + df = dict_factory + rv = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in val.items() + ) + else: + rv = val + if value_serializer is not None: + rv = value_serializer(None, None, rv) + + return rv + + +def astuple( + inst, + recurse=True, + filter=None, + tuple_factory=tuple, + retain_collection_types=False, +): + """ + Return the ``attrs`` attribute values of *inst* as a tuple. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the `attrs.Attribute` as the first argument and the + value as the second argument. + :param callable tuple_factory: A callable to produce tuples from. For + example, to produce lists instead of tuples. + :param bool retain_collection_types: Do not convert to ``list`` + or ``dict`` when encountering an attribute which type is + ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is + ``True``. + + :rtype: return type of *tuple_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.2.0 + """ + attrs = fields(inst.__class__) + rv = [] + retain = retain_collection_types # Very long. :/ + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv.append( + astuple( + v, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain is True else list + rv.append( + cf( + [ + astuple( + j, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(j.__class__) + else j + for j in v + ] + ) + ) + elif isinstance(v, dict): + df = v.__class__ if retain is True else dict + rv.append( + df( + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(kk.__class__) + else kk, + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(vv.__class__) + else vv, + ) + for kk, vv in v.items() + ) + ) + else: + rv.append(v) + else: + rv.append(v) + + return rv if tuple_factory is list else tuple_factory(rv) + + +def has(cls): + """ + Check whether *cls* is a class with ``attrs`` attributes. + + :param type cls: Class to introspect. + :raise TypeError: If *cls* is not a class. + + :rtype: bool + """ + return getattr(cls, "__attrs_attrs__", None) is not None + + +def assoc(inst, **changes): + """ + Copy *inst* and apply *changes*. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't + be found on *cls*. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. deprecated:: 17.1.0 + Use `attrs.evolve` instead if you can. + This function will not be removed du to the slightly different approach + compared to `attrs.evolve`. + """ + import warnings + + warnings.warn( + "assoc is deprecated and will be removed after 2018/01.", + DeprecationWarning, + stacklevel=2, + ) + new = copy.copy(inst) + attrs = fields(inst.__class__) + for k, v in changes.items(): + a = getattr(attrs, k, NOTHING) + if a is NOTHING: + raise AttrsAttributeNotFoundError( + f"{k} is not an attrs attribute on {new.__class__}." + ) + _obj_setattr(new, k, v) + return new + + +def evolve(inst, **changes): + """ + Create a new instance, based on *inst* with *changes* applied. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise TypeError: If *attr_name* couldn't be found in the class + ``__init__``. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 17.1.0 + """ + cls = inst.__class__ + attrs = fields(cls) + for a in attrs: + if not a.init: + continue + attr_name = a.name # To deal with private attributes. + init_name = a.alias + if init_name not in changes: + changes[init_name] = getattr(inst, attr_name) + + return cls(**changes) + + +def resolve_types(cls, globalns=None, localns=None, attribs=None): + """ + Resolve any strings and forward annotations in type annotations. + + This is only required if you need concrete types in `Attribute`'s *type* + field. In other words, you don't need to resolve your types if you only + use them for static type checking. + + With no arguments, names will be looked up in the module in which the class + was created. If this is not what you want, e.g. if the name only exists + inside a method, you may pass *globalns* or *localns* to specify other + dictionaries in which to look up these names. See the docs of + `typing.get_type_hints` for more details. + + :param type cls: Class to resolve. + :param Optional[dict] globalns: Dictionary containing global variables. + :param Optional[dict] localns: Dictionary containing local variables. + :param Optional[list] attribs: List of attribs for the given class. + This is necessary when calling from inside a ``field_transformer`` + since *cls* is not an ``attrs`` class yet. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class and you didn't pass any attribs. + :raise NameError: If types cannot be resolved because of missing variables. + + :returns: *cls* so you can use this function also as a class decorator. + Please note that you have to apply it **after** `attrs.define`. That + means the decorator has to come in the line **before** `attrs.define`. + + .. versionadded:: 20.1.0 + .. versionadded:: 21.1.0 *attribs* + + """ + # Since calling get_type_hints is expensive we cache whether we've + # done it already. + if getattr(cls, "__attrs_types_resolved__", None) != cls: + import typing + + hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) + for field in fields(cls) if attribs is None else attribs: + if field.name in hints: + # Since fields have been frozen we must work around it. + _obj_setattr(field, "type", hints[field.name]) + # We store the class we resolved so that subclasses know they haven't + # been resolved. + cls.__attrs_types_resolved__ = cls + + # Return the class so you can use it as a decorator too. + return cls diff --git a/lib/attr/_make.py b/lib/attr/_make.py new file mode 100644 index 0000000..9ee2200 --- /dev/null +++ b/lib/attr/_make.py @@ -0,0 +1,2965 @@ +# SPDX-License-Identifier: MIT + +import copy +import enum +import linecache +import sys +import types +import typing + +from operator import itemgetter + +# We need to import _compat itself in addition to the _compat members to avoid +# having the thread-local in the globals here. +from . import _compat, _config, setters +from ._compat import PY310, PYPY, _AnnotationExtractor, set_closure_cell +from .exceptions import ( + DefaultAlreadySetError, + FrozenInstanceError, + NotAnAttrsClassError, + UnannotatedAttributeError, +) + + +# This is used at least twice, so cache it here. +_obj_setattr = object.__setattr__ +_init_converter_pat = "__attr_converter_%s" +_init_factory_pat = "__attr_factory_%s" +_classvar_prefixes = ( + "typing.ClassVar", + "t.ClassVar", + "ClassVar", + "typing_extensions.ClassVar", +) +# we don't use a double-underscore prefix because that triggers +# name mangling when trying to create a slot for the field +# (when slots=True) +_hash_cache_field = "_attrs_cached_hash" + +_empty_metadata_singleton = types.MappingProxyType({}) + +# Unique object for unequivocal getattr() defaults. +_sentinel = object() + +_ng_default_on_setattr = setters.pipe(setters.convert, setters.validate) + + +class _Nothing(enum.Enum): + """ + Sentinel to indicate the lack of a value when ``None`` is ambiguous. + + If extending attrs, you can use ``typing.Literal[NOTHING]`` to show + that a value may be ``NOTHING``. + + .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. + .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant. + """ + + NOTHING = enum.auto() + + def __repr__(self): + return "NOTHING" + + def __bool__(self): + return False + + +NOTHING = _Nothing.NOTHING +""" +Sentinel to indicate the lack of a value when ``None`` is ambiguous. +""" + + +class _CacheHashWrapper(int): + """ + An integer subclass that pickles / copies as None + + This is used for non-slots classes with ``cache_hash=True``, to avoid + serializing a potentially (even likely) invalid hash value. Since ``None`` + is the default value for uncalculated hashes, whenever this is copied, + the copy's value for the hash should automatically reset. + + See GH #613 for more details. + """ + + def __reduce__(self, _none_constructor=type(None), _args=()): + return _none_constructor, _args + + +def attrib( + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, + alias=None, +): + """ + Create a new attribute on a class. + + .. warning:: + + Does *not* do anything unless the class is also decorated with + `attr.s`! + + :param default: A value that is used if an ``attrs``-generated ``__init__`` + is used and no value is passed while instantiating or the attribute is + excluded using ``init=False``. + + If the value is an instance of `attrs.Factory`, its callable will be + used to construct a new value (useful for mutable data types like lists + or dicts). + + If a default is not set (or set manually to `attrs.NOTHING`), a value + *must* be supplied when instantiating; otherwise a `TypeError` + will be raised. + + The default can also be set using decorator notation as shown below. + + :type default: Any value + + :param callable factory: Syntactic sugar for + ``default=attr.Factory(factory)``. + + :param validator: `callable` that is called by ``attrs``-generated + ``__init__`` methods after the instance has been initialized. They + receive the initialized instance, the :func:`~attrs.Attribute`, and the + passed value. + + The return value is *not* inspected so the validator has to throw an + exception itself. + + If a `list` is passed, its items are treated as validators and must + all pass. + + Validators can be globally disabled and re-enabled using + `get_run_validators`. + + The validator can also be set using decorator notation as shown below. + + :type validator: `callable` or a `list` of `callable`\\ s. + + :param repr: Include this attribute in the generated ``__repr__`` + method. If ``True``, include the attribute; if ``False``, omit it. By + default, the built-in ``repr()`` function is used. To override how the + attribute value is formatted, pass a ``callable`` that takes a single + value and returns a string. Note that the resulting string is used + as-is, i.e. it will be used directly *instead* of calling ``repr()`` + (the default). + :type repr: a `bool` or a `callable` to use a custom function. + + :param eq: If ``True`` (default), include this attribute in the + generated ``__eq__`` and ``__ne__`` methods that check two instances + for equality. To override how the attribute value is compared, + pass a ``callable`` that takes a single value and returns the value + to be compared. + :type eq: a `bool` or a `callable`. + + :param order: If ``True`` (default), include this attributes in the + generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. + To override how the attribute value is ordered, + pass a ``callable`` that takes a single value and returns the value + to be ordered. + :type order: a `bool` or a `callable`. + + :param cmp: Setting *cmp* is equivalent to setting *eq* and *order* to the + same value. Must not be mixed with *eq* or *order*. + :type cmp: a `bool` or a `callable`. + + :param Optional[bool] hash: Include this attribute in the generated + ``__hash__`` method. If ``None`` (default), mirror *eq*'s value. This + is the correct behavior according the Python spec. Setting this value + to anything else than ``None`` is *discouraged*. + :param bool init: Include this attribute in the generated ``__init__`` + method. It is possible to set this to ``False`` and set a default + value. In that case this attributed is unconditionally initialized + with the specified default value or factory. + :param callable converter: `callable` that is called by + ``attrs``-generated ``__init__`` methods to convert attribute's value + to the desired format. It is given the passed-in value, and the + returned value will be used as the new value of the attribute. The + value is converted before being passed to the validator, if any. + :param metadata: An arbitrary mapping, to be used by third-party + components. See `extending-metadata`. + + :param type: The type of the attribute. Nowadays, the preferred method to + specify the type is using a variable annotation (see :pep:`526`). + This argument is provided for backward compatibility. + Regardless of the approach used, the type will be stored on + ``Attribute.type``. + + Please note that ``attrs`` doesn't do anything with this metadata by + itself. You can use it as part of your own code or for + `static type checking `. + :param kw_only: Make this attribute keyword-only in the generated + ``__init__`` (if ``init`` is ``False``, this parameter is ignored). + :param on_setattr: Allows to overwrite the *on_setattr* setting from + `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used. + Set to `attrs.setters.NO_OP` to run **no** `setattr` hooks for this + attribute -- regardless of the setting in `attr.s`. + :type on_setattr: `callable`, or a list of callables, or `None`, or + `attrs.setters.NO_OP` + :param Optional[str] alias: Override this attribute's parameter name in the + generated ``__init__`` method. If left `None`, default to ``name`` + stripped of leading underscores. See `private-attributes`. + + .. versionadded:: 15.2.0 *convert* + .. versionadded:: 16.3.0 *metadata* + .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. + .. versionchanged:: 17.1.0 + *hash* is ``None`` and therefore mirrors *eq* by default. + .. versionadded:: 17.3.0 *type* + .. deprecated:: 17.4.0 *convert* + .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated + *convert* to achieve consistency with other noun-based arguments. + .. versionadded:: 18.1.0 + ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. + .. versionadded:: 18.2.0 *kw_only* + .. versionchanged:: 19.2.0 *convert* keyword argument removed. + .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 22.2.0 *alias* + """ + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq, order, True + ) + + if hash is not None and hash is not True and hash is not False: + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + + if factory is not None: + if default is not NOTHING: + raise ValueError( + "The `default` and `factory` arguments are mutually " + "exclusive." + ) + if not callable(factory): + raise ValueError("The `factory` argument must be a callable.") + default = Factory(factory) + + if metadata is None: + metadata = {} + + # Apply syntactic sugar by auto-wrapping. + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + if validator and isinstance(validator, (list, tuple)): + validator = and_(*validator) + + if converter and isinstance(converter, (list, tuple)): + converter = pipe(*converter) + + return _CountingAttr( + default=default, + validator=validator, + repr=repr, + cmp=None, + hash=hash, + init=init, + converter=converter, + metadata=metadata, + type=type, + kw_only=kw_only, + eq=eq, + eq_key=eq_key, + order=order, + order_key=order_key, + on_setattr=on_setattr, + alias=alias, + ) + + +def _compile_and_eval(script, globs, locs=None, filename=""): + """ + "Exec" the script with the given global (globs) and local (locs) variables. + """ + bytecode = compile(script, filename, "exec") + eval(bytecode, globs, locs) + + +def _make_method(name, script, filename, globs): + """ + Create the method with the script given and return the method object. + """ + locs = {} + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + count = 1 + base_filename = filename + while True: + linecache_tuple = ( + len(script), + None, + script.splitlines(True), + filename, + ) + old_val = linecache.cache.setdefault(filename, linecache_tuple) + if old_val == linecache_tuple: + break + else: + filename = f"{base_filename[:-1]}-{count}>" + count += 1 + + _compile_and_eval(script, globs, locs, filename) + + return locs[name] + + +def _make_attr_tuple_class(cls_name, attr_names): + """ + Create a tuple subclass to hold `Attribute`s for an `attrs` class. + + The subclass is a bare tuple with properties for names. + + class MyClassAttributes(tuple): + __slots__ = () + x = property(itemgetter(0)) + """ + attr_class_name = f"{cls_name}Attributes" + attr_class_template = [ + f"class {attr_class_name}(tuple):", + " __slots__ = ()", + ] + if attr_names: + for i, attr_name in enumerate(attr_names): + attr_class_template.append( + f" {attr_name} = _attrs_property(_attrs_itemgetter({i}))" + ) + else: + attr_class_template.append(" pass") + globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property} + _compile_and_eval("\n".join(attr_class_template), globs) + return globs[attr_class_name] + + +# Tuple class for extracted attributes from a class definition. +# `base_attrs` is a subset of `attrs`. +_Attributes = _make_attr_tuple_class( + "_Attributes", + [ + # all attributes to build dunder methods for + "attrs", + # attributes that have been inherited + "base_attrs", + # map inherited attributes to their originating classes + "base_attrs_map", + ], +) + + +def _is_class_var(annot): + """ + Check whether *annot* is a typing.ClassVar. + + The string comparison hack is used to avoid evaluating all string + annotations which would put attrs-based classes at a performance + disadvantage compared to plain old classes. + """ + annot = str(annot) + + # Annotation can be quoted. + if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): + annot = annot[1:-1] + + return annot.startswith(_classvar_prefixes) + + +def _has_own_attribute(cls, attrib_name): + """ + Check whether *cls* defines *attrib_name* (and doesn't just inherit it). + """ + attr = getattr(cls, attrib_name, _sentinel) + if attr is _sentinel: + return False + + for base_cls in cls.__mro__[1:]: + a = getattr(base_cls, attrib_name, None) + if attr is a: + return False + + return True + + +def _get_annotations(cls): + """ + Get annotations for *cls*. + """ + if _has_own_attribute(cls, "__annotations__"): + return cls.__annotations__ + + return {} + + +def _collect_base_attrs(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in reversed(cls.__mro__[1:-1]): + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.inherited or a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + # For each name, only keep the freshest definition i.e. the furthest at the + # back. base_attr_map is fine because it gets overwritten with every new + # instance. + filtered = [] + seen = set() + for a in reversed(base_attrs): + if a.name in seen: + continue + filtered.insert(0, a) + seen.add(a.name) + + return filtered, base_attr_map + + +def _collect_base_attrs_broken(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + + N.B. *taken_attr_names* will be mutated. + + Adhere to the old incorrect behavior. + + Notably it collects from the front and considers inherited attributes which + leads to the buggy behavior reported in #428. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in cls.__mro__[1:-1]: + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) + taken_attr_names.add(a.name) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + return base_attrs, base_attr_map + + +def _transform_attrs( + cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer +): + """ + Transform all `_CountingAttr`s on a class into `Attribute`s. + + If *these* is passed, use that and don't look for them on the class. + + *collect_by_mro* is True, collect them in the correct MRO order, otherwise + use the old -- incorrect -- order. See #428. + + Return an `_Attributes`. + """ + cd = cls.__dict__ + anns = _get_annotations(cls) + + if these is not None: + ca_list = [(name, ca) for name, ca in these.items()] + elif auto_attribs is True: + ca_names = { + name + for name, attr in cd.items() + if isinstance(attr, _CountingAttr) + } + ca_list = [] + annot_names = set() + for attr_name, type in anns.items(): + if _is_class_var(type): + continue + annot_names.add(attr_name) + a = cd.get(attr_name, NOTHING) + + if not isinstance(a, _CountingAttr): + if a is NOTHING: + a = attrib() + else: + a = attrib(default=a) + ca_list.append((attr_name, a)) + + unannotated = ca_names - annot_names + if len(unannotated) > 0: + raise UnannotatedAttributeError( + "The following `attr.ib`s lack a type annotation: " + + ", ".join( + sorted(unannotated, key=lambda n: cd.get(n).counter) + ) + + "." + ) + else: + ca_list = sorted( + ( + (name, attr) + for name, attr in cd.items() + if isinstance(attr, _CountingAttr) + ), + key=lambda e: e[1].counter, + ) + + own_attrs = [ + Attribute.from_counting_attr( + name=attr_name, ca=ca, type=anns.get(attr_name) + ) + for attr_name, ca in ca_list + ] + + if collect_by_mro: + base_attrs, base_attr_map = _collect_base_attrs( + cls, {a.name for a in own_attrs} + ) + else: + base_attrs, base_attr_map = _collect_base_attrs_broken( + cls, {a.name for a in own_attrs} + ) + + if kw_only: + own_attrs = [a.evolve(kw_only=True) for a in own_attrs] + base_attrs = [a.evolve(kw_only=True) for a in base_attrs] + + attrs = base_attrs + own_attrs + + # Mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: + had_default = False + for a in (a for a in attrs if a.init is not False and a.kw_only is False): + if had_default is True and a.default is NOTHING: + raise ValueError( + "No mandatory attributes allowed after an attribute with a " + f"default value or factory. Attribute in question: {a!r}" + ) + + if had_default is False and a.default is not NOTHING: + had_default = True + + if field_transformer is not None: + attrs = field_transformer(cls, attrs) + + # Resolve default field alias after executing field_transformer. + # This allows field_transformer to differentiate between explicit vs + # default aliases and supply their own defaults. + attrs = [ + a.evolve(alias=_default_init_alias_for(a.name)) if not a.alias else a + for a in attrs + ] + + # Create AttrsClass *after* applying the field_transformer since it may + # add or remove attributes! + attr_names = [a.name for a in attrs] + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map)) + + +if PYPY: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError() + +else: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + raise FrozenInstanceError() + + +def _frozen_delattrs(self, name): + """ + Attached to frozen classes as __delattr__. + """ + raise FrozenInstanceError() + + +class _ClassBuilder: + """ + Iteratively build *one* class. + """ + + __slots__ = ( + "_attr_names", + "_attrs", + "_base_attr_map", + "_base_names", + "_cache_hash", + "_cls", + "_cls_dict", + "_delete_attribs", + "_frozen", + "_has_pre_init", + "_has_post_init", + "_is_exc", + "_on_setattr", + "_slots", + "_weakref_slot", + "_wrote_own_setattr", + "_has_custom_setattr", + ) + + def __init__( + self, + cls, + these, + slots, + frozen, + weakref_slot, + getstate_setstate, + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_custom_setattr, + field_transformer, + ): + attrs, base_attrs, base_map = _transform_attrs( + cls, + these, + auto_attribs, + kw_only, + collect_by_mro, + field_transformer, + ) + + self._cls = cls + self._cls_dict = dict(cls.__dict__) if slots else {} + self._attrs = attrs + self._base_names = {a.name for a in base_attrs} + self._base_attr_map = base_map + self._attr_names = tuple(a.name for a in attrs) + self._slots = slots + self._frozen = frozen + self._weakref_slot = weakref_slot + self._cache_hash = cache_hash + self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) + self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) + self._delete_attribs = not bool(these) + self._is_exc = is_exc + self._on_setattr = on_setattr + + self._has_custom_setattr = has_custom_setattr + self._wrote_own_setattr = False + + self._cls_dict["__attrs_attrs__"] = self._attrs + + if frozen: + self._cls_dict["__setattr__"] = _frozen_setattrs + self._cls_dict["__delattr__"] = _frozen_delattrs + + self._wrote_own_setattr = True + elif on_setattr in ( + _ng_default_on_setattr, + setters.validate, + setters.convert, + ): + has_validator = has_converter = False + for a in attrs: + if a.validator is not None: + has_validator = True + if a.converter is not None: + has_converter = True + + if has_validator and has_converter: + break + if ( + ( + on_setattr == _ng_default_on_setattr + and not (has_validator or has_converter) + ) + or (on_setattr == setters.validate and not has_validator) + or (on_setattr == setters.convert and not has_converter) + ): + # If class-level on_setattr is set to convert + validate, but + # there's no field to convert or validate, pretend like there's + # no on_setattr. + self._on_setattr = None + + if getstate_setstate: + ( + self._cls_dict["__getstate__"], + self._cls_dict["__setstate__"], + ) = self._make_getstate_setstate() + + def __repr__(self): + return f"<_ClassBuilder(cls={self._cls.__name__})>" + + if PY310: + import abc + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + if self._slots is True: + return self._create_slots_class() + + return self.abc.update_abstractmethods( + self._patch_original_class() + ) + + else: + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + if self._slots is True: + return self._create_slots_class() + + return self._patch_original_class() + + def _patch_original_class(self): + """ + Apply accumulated methods and return the class. + """ + cls = self._cls + base_names = self._base_names + + # Clean class of attribute definitions (`attr.ib()`s). + if self._delete_attribs: + for name in self._attr_names: + if ( + name not in base_names + and getattr(cls, name, _sentinel) is not _sentinel + ): + try: + delattr(cls, name) + except AttributeError: + # This can happen if a base class defines a class + # variable and we want to set an attribute with the + # same name by using only a type annotation. + pass + + # Attach our dunder methods. + for name, value in self._cls_dict.items(): + setattr(cls, name, value) + + # If we've inherited an attrs __setattr__ and don't write our own, + # reset it to object's. + if not self._wrote_own_setattr and getattr( + cls, "__attrs_own_setattr__", False + ): + cls.__attrs_own_setattr__ = False + + if not self._has_custom_setattr: + cls.__setattr__ = _obj_setattr + + return cls + + def _create_slots_class(self): + """ + Build and return a new class with a `__slots__` attribute. + """ + cd = { + k: v + for k, v in self._cls_dict.items() + if k not in tuple(self._attr_names) + ("__dict__", "__weakref__") + } + + # If our class doesn't have its own implementation of __setattr__ + # (either from the user or by us), check the bases, if one of them has + # an attrs-made __setattr__, that needs to be reset. We don't walk the + # MRO because we only care about our immediate base classes. + # XXX: This can be confused by subclassing a slotted attrs class with + # XXX: a non-attrs class and subclass the resulting class with an attrs + # XXX: class. See `test_slotted_confused` for details. For now that's + # XXX: OK with us. + if not self._wrote_own_setattr: + cd["__attrs_own_setattr__"] = False + + if not self._has_custom_setattr: + for base_cls in self._cls.__bases__: + if base_cls.__dict__.get("__attrs_own_setattr__", False): + cd["__setattr__"] = _obj_setattr + break + + # Traverse the MRO to collect existing slots + # and check for an existing __weakref__. + existing_slots = dict() + weakref_inherited = False + for base_cls in self._cls.__mro__[1:-1]: + if base_cls.__dict__.get("__weakref__", None) is not None: + weakref_inherited = True + existing_slots.update( + { + name: getattr(base_cls, name) + for name in getattr(base_cls, "__slots__", []) + } + ) + + base_names = set(self._base_names) + + names = self._attr_names + if ( + self._weakref_slot + and "__weakref__" not in getattr(self._cls, "__slots__", ()) + and "__weakref__" not in names + and not weakref_inherited + ): + names += ("__weakref__",) + + # We only add the names of attributes that aren't inherited. + # Setting __slots__ to inherited attributes wastes memory. + slot_names = [name for name in names if name not in base_names] + # There are slots for attributes from current class + # that are defined in parent classes. + # As their descriptors may be overridden by a child class, + # we collect them here and update the class dict + reused_slots = { + slot: slot_descriptor + for slot, slot_descriptor in existing_slots.items() + if slot in slot_names + } + slot_names = [name for name in slot_names if name not in reused_slots] + cd.update(reused_slots) + if self._cache_hash: + slot_names.append(_hash_cache_field) + cd["__slots__"] = tuple(slot_names) + + cd["__qualname__"] = self._cls.__qualname__ + + # Create new class based on old class and our methods. + cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) + + # The following is a fix for + # . + # If a method mentions `__class__` or uses the no-arg super(), the + # compiler will bake a reference to the class in the method itself + # as `method.__closure__`. Since we replace the class with a + # clone, we rewrite these references so it keeps working. + for item in cls.__dict__.values(): + if isinstance(item, (classmethod, staticmethod)): + # Class- and staticmethods hide their functions inside. + # These might need to be rewritten as well. + closure_cells = getattr(item.__func__, "__closure__", None) + elif isinstance(item, property): + # Workaround for property `super()` shortcut (PY3-only). + # There is no universal way for other descriptors. + closure_cells = getattr(item.fget, "__closure__", None) + else: + closure_cells = getattr(item, "__closure__", None) + + if not closure_cells: # Catch None or the empty list. + continue + for cell in closure_cells: + try: + match = cell.cell_contents is self._cls + except ValueError: # ValueError: Cell is empty + pass + else: + if match: + set_closure_cell(cell, cls) + + return cls + + def add_repr(self, ns): + self._cls_dict["__repr__"] = self._add_method_dunders( + _make_repr(self._attrs, ns, self._cls) + ) + return self + + def add_str(self): + repr = self._cls_dict.get("__repr__") + if repr is None: + raise ValueError( + "__str__ can only be generated if a __repr__ exists." + ) + + def __str__(self): + return self.__repr__() + + self._cls_dict["__str__"] = self._add_method_dunders(__str__) + return self + + def _make_getstate_setstate(self): + """ + Create custom __setstate__ and __getstate__ methods. + """ + # __weakref__ is not writable. + state_attr_names = tuple( + an for an in self._attr_names if an != "__weakref__" + ) + + def slots_getstate(self): + """ + Automatically created by attrs. + """ + return {name: getattr(self, name) for name in state_attr_names} + + hash_caching_enabled = self._cache_hash + + def slots_setstate(self, state): + """ + Automatically created by attrs. + """ + __bound_setattr = _obj_setattr.__get__(self) + for name in state_attr_names: + if name in state: + __bound_setattr(name, state[name]) + + # The hash code cache is not included when the object is + # serialized, but it still needs to be initialized to None to + # indicate that the first call to __hash__ should be a cache + # miss. + if hash_caching_enabled: + __bound_setattr(_hash_cache_field, None) + + return slots_getstate, slots_setstate + + def make_unhashable(self): + self._cls_dict["__hash__"] = None + return self + + def add_hash(self): + self._cls_dict["__hash__"] = self._add_method_dunders( + _make_hash( + self._cls, + self._attrs, + frozen=self._frozen, + cache_hash=self._cache_hash, + ) + ) + + return self + + def add_init(self): + self._cls_dict["__init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_pre_init, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=False, + ) + ) + + return self + + def add_match_args(self): + self._cls_dict["__match_args__"] = tuple( + field.name + for field in self._attrs + if field.init and not field.kw_only + ) + + def add_attrs_init(self): + self._cls_dict["__attrs_init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_pre_init, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=True, + ) + ) + + return self + + def add_eq(self): + cd = self._cls_dict + + cd["__eq__"] = self._add_method_dunders( + _make_eq(self._cls, self._attrs) + ) + cd["__ne__"] = self._add_method_dunders(_make_ne()) + + return self + + def add_order(self): + cd = self._cls_dict + + cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_order(self._cls, self._attrs) + ) + + return self + + def add_setattr(self): + if self._frozen: + return self + + sa_attrs = {} + for a in self._attrs: + on_setattr = a.on_setattr or self._on_setattr + if on_setattr and on_setattr is not setters.NO_OP: + sa_attrs[a.name] = a, on_setattr + + if not sa_attrs: + return self + + if self._has_custom_setattr: + # We need to write a __setattr__ but there already is one! + raise ValueError( + "Can't combine custom __setattr__ with on_setattr hooks." + ) + + # docstring comes from _add_method_dunders + def __setattr__(self, name, val): + try: + a, hook = sa_attrs[name] + except KeyError: + nval = val + else: + nval = hook(self, a, val) + + _obj_setattr(self, name, nval) + + self._cls_dict["__attrs_own_setattr__"] = True + self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) + self._wrote_own_setattr = True + + return self + + def _add_method_dunders(self, method): + """ + Add __module__ and __qualname__ to a *method* if possible. + """ + try: + method.__module__ = self._cls.__module__ + except AttributeError: + pass + + try: + method.__qualname__ = ".".join( + (self._cls.__qualname__, method.__name__) + ) + except AttributeError: + pass + + try: + method.__doc__ = ( + "Method generated by attrs for class " + f"{self._cls.__qualname__}." + ) + except AttributeError: + pass + + return method + + +def _determine_attrs_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + return cmp, cmp + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq = default_eq + + if order is None: + order = eq + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, order + + +def _determine_attrib_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + def decide_callable_or_boolean(value): + """ + Decide whether a key function is used. + """ + if callable(value): + value, key = True, value + else: + key = None + return value, key + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + cmp, cmp_key = decide_callable_or_boolean(cmp) + return cmp, cmp_key, cmp, cmp_key + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq, eq_key = default_eq, None + else: + eq, eq_key = decide_callable_or_boolean(eq) + + if order is None: + order, order_key = eq, eq_key + else: + order, order_key = decide_callable_or_boolean(order) + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, eq_key, order, order_key + + +def _determine_whether_to_implement( + cls, flag, auto_detect, dunders, default=True +): + """ + Check whether we should implement a set of methods for *cls*. + + *flag* is the argument passed into @attr.s like 'init', *auto_detect* the + same as passed into @attr.s and *dunders* is a tuple of attribute names + whose presence signal that the user has implemented it themselves. + + Return *default* if no reason for either for or against is found. + """ + if flag is True or flag is False: + return flag + + if flag is None and auto_detect is False: + return default + + # Logically, flag is None and auto_detect is True here. + for dunder in dunders: + if _has_own_attribute(cls, dunder): + return False + + return default + + +def attrs( + maybe_cls=None, + these=None, + repr_ns=None, + repr=None, + cmp=None, + hash=None, + init=None, + slots=False, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=False, + kw_only=False, + cache_hash=False, + auto_exc=False, + eq=None, + order=None, + auto_detect=False, + collect_by_mro=False, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, + unsafe_hash=None, +): + r""" + A class decorator that adds :term:`dunder methods` according to the + specified attributes using `attr.ib` or the *these* argument. + + :param these: A dictionary of name to `attr.ib` mappings. This is + useful to avoid the definition of your attributes within the class body + because you can't (e.g. if you want to add ``__repr__`` methods to + Django models) or don't want to. + + If *these* is not ``None``, ``attrs`` will *not* search the class body + for attributes and will *not* remove any attributes from it. + + The order is deduced from the order of the attributes inside *these*. + + :type these: `dict` of `str` to `attr.ib` + + :param str repr_ns: When using nested classes, there's no way in Python 2 + to automatically detect that. Therefore it's possible to set the + namespace explicitly for a more meaningful ``repr`` output. + :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*, + *order*, and *hash* arguments explicitly, assume they are set to + ``True`` **unless any** of the involved methods for one of the + arguments is implemented in the *current* class (i.e. it is *not* + inherited from some base class). + + So for example by implementing ``__eq__`` on a class yourself, + ``attrs`` will deduce ``eq=False`` and will create *neither* + ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible + ``__ne__`` by default, so it *should* be enough to only implement + ``__eq__`` in most cases). + + .. warning:: + + If you prevent ``attrs`` from creating the ordering methods for you + (``order=False``, e.g. by implementing ``__le__``), it becomes + *your* responsibility to make sure its ordering is sound. The best + way is to use the `functools.total_ordering` decorator. + + + Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*, + *cmp*, or *hash* overrides whatever *auto_detect* would determine. + + :param bool repr: Create a ``__repr__`` method with a human readable + representation of ``attrs`` attributes.. + :param bool str: Create a ``__str__`` method that is identical to + ``__repr__``. This is usually not necessary except for + `Exception`\ s. + :param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__`` + and ``__ne__`` methods that check two instances for equality. + + They compare the instances as if they were tuples of their ``attrs`` + attributes if and only if the types of both classes are *identical*! + :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``, + ``__gt__``, and ``__ge__`` methods that behave like *eq* above and + allow instances to be ordered. If ``None`` (default) mirror value of + *eq*. + :param Optional[bool] cmp: Setting *cmp* is equivalent to setting *eq* + and *order* to the same value. Must not be mixed with *eq* or *order*. + :param Optional[bool] unsafe_hash: If ``None`` (default), the ``__hash__`` + method is generated according how *eq* and *frozen* are set. + + 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. + 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to + None, marking it unhashable (which it is). + 3. If *eq* is False, ``__hash__`` will be left untouched meaning the + ``__hash__`` method of the base class will be used (if base class is + ``object``, this means it will fall back to id-based hashing.). + + Although not recommended, you can decide for yourself and force + ``attrs`` to create one (e.g. if the class is immutable even though you + didn't freeze it programmatically) by passing ``True`` or not. Both of + these cases are rather special and should be used carefully. + + See our documentation on `hashing`, Python's documentation on + `object.__hash__`, and the `GitHub issue that led to the default \ + behavior `_ for more + details. + :param Optional[bool] hash: Alias for *unsafe_hash*. *unsafe_hash* takes + precedence. + :param bool init: Create a ``__init__`` method that initializes the + ``attrs`` attributes. Leading underscores are stripped for the argument + name. If a ``__attrs_pre_init__`` method exists on the class, it will + be called before the class is initialized. If a ``__attrs_post_init__`` + method exists on the class, it will be called after the class is fully + initialized. + + If ``init`` is ``False``, an ``__attrs_init__`` method will be + injected instead. This allows you to define a custom ``__init__`` + method that can do pre-init work such as ``super().__init__()``, + and then call ``__attrs_init__()`` and ``__attrs_post_init__()``. + :param bool slots: Create a :term:`slotted class ` that's + more memory-efficient. Slotted classes are generally superior to the + default dict classes, but have some gotchas you should know about, so + we encourage you to read the :term:`glossary entry `. + :param bool frozen: Make instances immutable after initialization. If + someone attempts to modify a frozen instance, + `attr.exceptions.FrozenInstanceError` is raised. + + .. note:: + + 1. This is achieved by installing a custom ``__setattr__`` method + on your class, so you can't implement your own. + + 2. True immutability is impossible in Python. + + 3. This *does* have a minor a runtime performance `impact + ` when initializing new instances. In other words: + ``__init__`` is slightly slower with ``frozen=True``. + + 4. If a class is frozen, you cannot modify ``self`` in + ``__attrs_post_init__`` or a self-written ``__init__``. You can + circumvent that limitation by using + ``object.__setattr__(self, "attribute_name", value)``. + + 5. Subclasses of a frozen class are frozen too. + + :param bool weakref_slot: Make instances weak-referenceable. This has no + effect unless ``slots`` is also enabled. + :param bool auto_attribs: If ``True``, collect :pep:`526`-annotated + attributes from the class body. + + In this case, you **must** annotate every field. If ``attrs`` + encounters a field that is set to an `attr.ib` but lacks a type + annotation, an `attr.exceptions.UnannotatedAttributeError` is + raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't + want to set a type. + + If you assign a value to those attributes (e.g. ``x: int = 42``), that + value becomes the default value like if it were passed using + ``attr.ib(default=42)``. Passing an instance of `attrs.Factory` also + works as expected in most cases (see warning below). + + Attributes annotated as `typing.ClassVar`, and attributes that are + neither annotated nor set to an `attr.ib` are **ignored**. + + .. warning:: + For features that use the attribute name to create decorators (e.g. + `validators `), you still *must* assign `attr.ib` to + them. Otherwise Python will either not find the name or try to use + the default value to call e.g. ``validator`` on it. + + These errors can be quite confusing and probably the most common bug + report on our bug tracker. + + :param bool kw_only: Make all attributes keyword-only + in the generated ``__init__`` (if ``init`` is ``False``, this + parameter is ignored). + :param bool cache_hash: Ensure that the object's hash code is computed + only once and stored on the object. If this is set to ``True``, + hashing must be either explicitly or implicitly enabled for this + class. If the hash code is cached, avoid any reassignments of + fields involved in hash code computation or mutations of the objects + those fields point to after object creation. If such changes occur, + the behavior of the object's hash code is undefined. + :param bool auto_exc: If the class subclasses `BaseException` + (which implicitly includes any subclass of any exception), the + following happens to behave like a well-behaved Python exceptions + class: + + - the values for *eq*, *order*, and *hash* are ignored and the + instances compare and hash by the instance's ids (N.B. ``attrs`` will + *not* remove existing implementations of ``__hash__`` or the equality + methods. It just won't add own ones.), + - all attributes that are either passed into ``__init__`` or have a + default value are additionally available as a tuple in the ``args`` + attribute, + - the value of *str* is ignored leaving ``__str__`` to base classes. + :param bool collect_by_mro: Setting this to `True` fixes the way ``attrs`` + collects attributes from base classes. The default behavior is + incorrect in certain cases of multiple inheritance. It should be on by + default but is kept off for backward-compatibility. + + See issue `#428 `_ for + more details. + + :param Optional[bool] getstate_setstate: + .. note:: + This is usually only interesting for slotted classes and you should + probably just set *auto_detect* to `True`. + + If `True`, ``__getstate__`` and + ``__setstate__`` are generated and attached to the class. This is + necessary for slotted classes to be pickleable. If left `None`, it's + `True` by default for slotted classes and ``False`` for dict classes. + + If *auto_detect* is `True`, and *getstate_setstate* is left `None`, + and **either** ``__getstate__`` or ``__setstate__`` is detected directly + on the class (i.e. not inherited), it is set to `False` (this is usually + what you want). + + :param on_setattr: A callable that is run whenever the user attempts to set + an attribute (either by assignment like ``i.x = 42`` or by using + `setattr` like ``setattr(i, "x", 42)``). It receives the same arguments + as validators: the instance, the attribute that is being modified, and + the new value. + + If no exception is raised, the attribute is set to the return value of + the callable. + + If a list of callables is passed, they're automatically wrapped in an + `attrs.setters.pipe`. + :type on_setattr: `callable`, or a list of callables, or `None`, or + `attrs.setters.NO_OP` + + :param Optional[callable] field_transformer: + A function that is called with the original class object and all + fields right before ``attrs`` finalizes the class. You can use + this, e.g., to automatically add converters or validators to + fields based on their types. See `transform-fields` for more details. + + :param bool match_args: + If `True` (default), set ``__match_args__`` on the class to support + :pep:`634` (Structural Pattern Matching). It is a tuple of all + non-keyword-only ``__init__`` parameter names on Python 3.10 and later. + Ignored on older Python versions. + + .. versionadded:: 16.0.0 *slots* + .. versionadded:: 16.1.0 *frozen* + .. versionadded:: 16.3.0 *str* + .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. + .. versionchanged:: 17.1.0 + *hash* supports ``None`` as value which is also the default now. + .. versionadded:: 17.3.0 *auto_attribs* + .. versionchanged:: 18.1.0 + If *these* is passed, no attributes are deleted from the class body. + .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. + .. versionadded:: 18.2.0 *weakref_slot* + .. deprecated:: 18.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a + `DeprecationWarning` if the classes compared are subclasses of + each other. ``__eq`` and ``__ne__`` never tried to compared subclasses + to each other. + .. versionchanged:: 19.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider + subclasses comparable anymore. + .. versionadded:: 18.2.0 *kw_only* + .. versionadded:: 18.2.0 *cache_hash* + .. versionadded:: 19.1.0 *auto_exc* + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *auto_detect* + .. versionadded:: 20.1.0 *collect_by_mro* + .. versionadded:: 20.1.0 *getstate_setstate* + .. versionadded:: 20.1.0 *on_setattr* + .. versionadded:: 20.3.0 *field_transformer* + .. versionchanged:: 21.1.0 + ``init=False`` injects ``__attrs_init__`` + .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 21.3.0 *match_args* + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + """ + eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) + + # unsafe_hash takes precedence due to PEP 681. + if unsafe_hash is not None: + hash = unsafe_hash + + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + def wrap(cls): + is_frozen = frozen or _has_frozen_base_class(cls) + is_exc = auto_exc is True and issubclass(cls, BaseException) + has_own_setattr = auto_detect and _has_own_attribute( + cls, "__setattr__" + ) + + if has_own_setattr and is_frozen: + raise ValueError("Can't freeze a class with a custom __setattr__.") + + builder = _ClassBuilder( + cls, + these, + slots, + is_frozen, + weakref_slot, + _determine_whether_to_implement( + cls, + getstate_setstate, + auto_detect, + ("__getstate__", "__setstate__"), + default=slots, + ), + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_own_setattr, + field_transformer, + ) + if _determine_whether_to_implement( + cls, repr, auto_detect, ("__repr__",) + ): + builder.add_repr(repr_ns) + if str is True: + builder.add_str() + + eq = _determine_whether_to_implement( + cls, eq_, auto_detect, ("__eq__", "__ne__") + ) + if not is_exc and eq is True: + builder.add_eq() + if not is_exc and _determine_whether_to_implement( + cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") + ): + builder.add_order() + + builder.add_setattr() + + nonlocal hash + if ( + hash is None + and auto_detect is True + and _has_own_attribute(cls, "__hash__") + ): + hash = False + + if hash is not True and hash is not False and hash is not None: + # Can't use `hash in` because 1 == True for example. + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + elif hash is False or (hash is None and eq is False) or is_exc: + # Don't do anything. Should fall back to __object__'s __hash__ + # which is by id. + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " hashing must be either explicitly or implicitly " + "enabled." + ) + elif hash is True or ( + hash is None and eq is True and is_frozen is True + ): + # Build a __hash__ if told so, or if it's safe. + builder.add_hash() + else: + # Raise TypeError on attempts to hash. + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " hashing must be either explicitly or implicitly " + "enabled." + ) + builder.make_unhashable() + + if _determine_whether_to_implement( + cls, init, auto_detect, ("__init__",) + ): + builder.add_init() + else: + builder.add_attrs_init() + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " init must be True." + ) + + if ( + PY310 + and match_args + and not _has_own_attribute(cls, "__match_args__") + ): + builder.add_match_args() + + return builder.build_class() + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but ``None`` if used as `@attrs()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +_attrs = attrs +""" +Internal alias so we can use it in functions that take an argument called +*attrs*. +""" + + +def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ is _frozen_setattrs + + +def _generate_unique_filename(cls, func_name): + """ + Create a "filename" suitable for a function being generated. + """ + return ( + f"" + ) + + +def _make_hash(cls, attrs, frozen, cache_hash): + attrs = tuple( + a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) + ) + + tab = " " + + unique_filename = _generate_unique_filename(cls, "hash") + type_hash = hash(unique_filename) + # If eq is custom generated, we need to include the functions in globs + globs = {} + + hash_def = "def __hash__(self" + hash_func = "hash((" + closing_braces = "))" + if not cache_hash: + hash_def += "):" + else: + hash_def += ", *" + + hash_def += ( + ", _cache_wrapper=" + + "__import__('attr._make')._make._CacheHashWrapper):" + ) + hash_func = "_cache_wrapper(" + hash_func + closing_braces += ")" + + method_lines = [hash_def] + + def append_hash_computation_lines(prefix, indent): + """ + Generate the code for actually computing the hash code. + Below this will either be returned directly or used to compute + a value which is then cached, depending on the value of cache_hash + """ + + method_lines.extend( + [ + indent + prefix + hash_func, + indent + f" {type_hash},", + ] + ) + + for a in attrs: + if a.eq_key: + cmp_name = f"_{a.name}_key" + globs[cmp_name] = a.eq_key + method_lines.append( + indent + f" {cmp_name}(self.{a.name})," + ) + else: + method_lines.append(indent + f" self.{a.name},") + + method_lines.append(indent + " " + closing_braces) + + if cache_hash: + method_lines.append(tab + f"if self.{_hash_cache_field} is None:") + if frozen: + append_hash_computation_lines( + f"object.__setattr__(self, '{_hash_cache_field}', ", tab * 2 + ) + method_lines.append(tab * 2 + ")") # close __setattr__ + else: + append_hash_computation_lines( + f"self.{_hash_cache_field} = ", tab * 2 + ) + method_lines.append(tab + f"return self.{_hash_cache_field}") + else: + append_hash_computation_lines("return ", tab) + + script = "\n".join(method_lines) + return _make_method("__hash__", script, unique_filename, globs) + + +def _add_hash(cls, attrs): + """ + Add a hash method to *cls*. + """ + cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False) + return cls + + +def _make_ne(): + """ + Create __ne__ method. + """ + + def __ne__(self, other): + """ + Check equality and either forward a NotImplemented or + return the result negated. + """ + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + + return not result + + return __ne__ + + +def _make_eq(cls, attrs): + """ + Create __eq__ method for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.eq] + + unique_filename = _generate_unique_filename(cls, "eq") + lines = [ + "def __eq__(self, other):", + " if other.__class__ is not self.__class__:", + " return NotImplemented", + ] + + # We can't just do a big self.x = other.x and... clause due to + # irregularities like nan == nan is false but (nan,) == (nan,) is true. + globs = {} + if attrs: + lines.append(" return (") + others = [" ) == ("] + for a in attrs: + if a.eq_key: + cmp_name = f"_{a.name}_key" + # Add the key function to the global namespace + # of the evaluated function. + globs[cmp_name] = a.eq_key + lines.append(f" {cmp_name}(self.{a.name}),") + others.append(f" {cmp_name}(other.{a.name}),") + else: + lines.append(f" self.{a.name},") + others.append(f" other.{a.name},") + + lines += others + [" )"] + else: + lines.append(" return True") + + script = "\n".join(lines) + + return _make_method("__eq__", script, unique_filename, globs) + + +def _make_order(cls, attrs): + """ + Create ordering methods for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.order] + + def attrs_to_tuple(obj): + """ + Save us some typing. + """ + return tuple( + key(value) if key else value + for value, key in ( + (getattr(obj, a.name), a.order_key) for a in attrs + ) + ) + + def __lt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) < attrs_to_tuple(other) + + return NotImplemented + + def __le__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) <= attrs_to_tuple(other) + + return NotImplemented + + def __gt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) > attrs_to_tuple(other) + + return NotImplemented + + def __ge__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) >= attrs_to_tuple(other) + + return NotImplemented + + return __lt__, __le__, __gt__, __ge__ + + +def _add_eq(cls, attrs=None): + """ + Add equality methods to *cls* with *attrs*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__eq__ = _make_eq(cls, attrs) + cls.__ne__ = _make_ne() + + return cls + + +def _make_repr(attrs, ns, cls): + unique_filename = _generate_unique_filename(cls, "repr") + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, (repr if a.repr is True else a.repr), a.init) + for a in attrs + if a.repr is not False + ) + globs = { + name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr + } + globs["_compat"] = _compat + globs["AttributeError"] = AttributeError + globs["NOTHING"] = NOTHING + attribute_fragments = [] + for name, r, i in attr_names_with_reprs: + accessor = ( + "self." + name if i else 'getattr(self, "' + name + '", NOTHING)' + ) + fragment = ( + "%s={%s!r}" % (name, accessor) + if r == repr + else "%s={%s_repr(%s)}" % (name, name, accessor) + ) + attribute_fragments.append(fragment) + repr_fragment = ", ".join(attribute_fragments) + + if ns is None: + cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' + else: + cls_name_fragment = ns + ".{self.__class__.__name__}" + + lines = [ + "def __repr__(self):", + " try:", + " already_repring = _compat.repr_context.already_repring", + " except AttributeError:", + " already_repring = {id(self),}", + " _compat.repr_context.already_repring = already_repring", + " else:", + " if id(self) in already_repring:", + " return '...'", + " else:", + " already_repring.add(id(self))", + " try:", + f" return f'{cls_name_fragment}({repr_fragment})'", + " finally:", + " already_repring.remove(id(self))", + ] + + return _make_method( + "__repr__", "\n".join(lines), unique_filename, globs=globs + ) + + +def _add_repr(cls, ns=None, attrs=None): + """ + Add a repr method to *cls*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__repr__ = _make_repr(attrs, ns, cls) + return cls + + +def fields(cls): + """ + Return the tuple of ``attrs`` attributes for a class. + + The tuple also allows accessing the fields by their names (see below for + examples). + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: tuple (with name accessors) of `attrs.Attribute` + + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + """ + if not isinstance(cls, type): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError(f"{cls!r} is not an attrs-decorated class.") + return attrs + + +def fields_dict(cls): + """ + Return an ordered dictionary of ``attrs`` attributes for a class, whose + keys are the attribute names. + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: dict + + .. versionadded:: 18.1.0 + """ + if not isinstance(cls, type): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError(f"{cls!r} is not an attrs-decorated class.") + return {a.name: a for a in attrs} + + +def validate(inst): + """ + Validate all attributes on *inst* that have a validator. + + Leaves all exceptions through. + + :param inst: Instance of a class with ``attrs`` attributes. + """ + if _config._run_validators is False: + return + + for a in fields(inst.__class__): + v = a.validator + if v is not None: + v(inst, a, getattr(inst, a.name)) + + +def _is_slot_cls(cls): + return "__slots__" in cls.__dict__ + + +def _is_slot_attr(a_name, base_attr_map): + """ + Check if the attribute name comes from a slot class. + """ + return a_name in base_attr_map and _is_slot_cls(base_attr_map[a_name]) + + +def _make_init( + cls, + attrs, + pre_init, + post_init, + frozen, + slots, + cache_hash, + base_attr_map, + is_exc, + cls_on_setattr, + attrs_init, +): + has_cls_on_setattr = ( + cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP + ) + + if frozen and has_cls_on_setattr: + raise ValueError("Frozen classes can't use on_setattr.") + + needs_cached_setattr = cache_hash or frozen + filtered_attrs = [] + attr_dict = {} + for a in attrs: + if not a.init and a.default is NOTHING: + continue + + filtered_attrs.append(a) + attr_dict[a.name] = a + + if a.on_setattr is not None: + if frozen is True: + raise ValueError("Frozen classes can't use on_setattr.") + + needs_cached_setattr = True + elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: + needs_cached_setattr = True + + unique_filename = _generate_unique_filename(cls, "init") + + script, globs, annotations = _attrs_to_init_script( + filtered_attrs, + frozen, + slots, + pre_init, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_cls_on_setattr, + attrs_init, + ) + if cls.__module__ in sys.modules: + # This makes typing.get_type_hints(CLS.__init__) resolve string types. + globs.update(sys.modules[cls.__module__].__dict__) + + globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) + + if needs_cached_setattr: + # Save the lookup overhead in __init__ if we need to circumvent + # setattr hooks. + globs["_cached_setattr_get"] = _obj_setattr.__get__ + + init = _make_method( + "__attrs_init__" if attrs_init else "__init__", + script, + unique_filename, + globs, + ) + init.__annotations__ = annotations + + return init + + +def _setattr(attr_name, value_var, has_on_setattr): + """ + Use the cached object.setattr to set *attr_name* to *value_var*. + """ + return f"_setattr('{attr_name}', {value_var})" + + +def _setattr_with_converter(attr_name, value_var, has_on_setattr): + """ + Use the cached object.setattr to set *attr_name* to *value_var*, but run + its converter first. + """ + return "_setattr('%s', %s(%s))" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + +def _assign(attr_name, value, has_on_setattr): + """ + Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise + relegate to _setattr. + """ + if has_on_setattr: + return _setattr(attr_name, value, True) + + return f"self.{attr_name} = {value}" + + +def _assign_with_converter(attr_name, value_var, has_on_setattr): + """ + Unless *attr_name* has an on_setattr hook, use normal assignment after + conversion. Otherwise relegate to _setattr_with_converter. + """ + if has_on_setattr: + return _setattr_with_converter(attr_name, value_var, True) + + return "self.%s = %s(%s)" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + +def _attrs_to_init_script( + attrs, + frozen, + slots, + pre_init, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_cls_on_setattr, + attrs_init, +): + """ + Return a script of an initializer for *attrs* and a dict of globals. + + The globals are expected by the generated script. + + If *frozen* is True, we cannot set the attributes directly so we use + a cached ``object.__setattr__``. + """ + lines = [] + if pre_init: + lines.append("self.__attrs_pre_init__()") + + if needs_cached_setattr: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. + # Note _setattr will be used again below if cache_hash is True + "_setattr = _cached_setattr_get(self)" + ) + + if frozen is True: + if slots is True: + fmt_setter = _setattr + fmt_setter_with_converter = _setattr_with_converter + else: + # Dict frozen classes assign directly to __dict__. + # But only if the attribute doesn't come from an ancestor slot + # class. + # Note _inst_dict will be used again below if cache_hash is True + lines.append("_inst_dict = self.__dict__") + + def fmt_setter(attr_name, value_var, has_on_setattr): + if _is_slot_attr(attr_name, base_attr_map): + return _setattr(attr_name, value_var, has_on_setattr) + + return f"_inst_dict['{attr_name}'] = {value_var}" + + def fmt_setter_with_converter( + attr_name, value_var, has_on_setattr + ): + if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): + return _setattr_with_converter( + attr_name, value_var, has_on_setattr + ) + + return "_inst_dict['%s'] = %s(%s)" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + else: + # Not frozen. + fmt_setter = _assign + fmt_setter_with_converter = _assign_with_converter + + args = [] + kw_only_args = [] + attrs_to_validate = [] + + # This is a dictionary of names to validator and converter callables. + # Injecting this into __init__ globals lets us avoid lookups. + names_for_globals = {} + annotations = {"return": None} + + for a in attrs: + if a.validator: + attrs_to_validate.append(a) + + attr_name = a.name + has_on_setattr = a.on_setattr is not None or ( + a.on_setattr is not setters.NO_OP and has_cls_on_setattr + ) + # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not + # explicitly provided + arg_name = a.alias + + has_factory = isinstance(a.default, Factory) + if has_factory and a.default.takes_self: + maybe_self = "self" + else: + maybe_self = "" + + if a.init is False: + if has_factory: + init_factory_name = _init_factory_pat % (a.name,) + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + init_factory_name + f"({maybe_self})", + has_on_setattr, + ) + ) + conv_name = _init_converter_pat % (a.name,) + names_for_globals[conv_name] = a.converter + else: + lines.append( + fmt_setter( + attr_name, + init_factory_name + f"({maybe_self})", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, + ) + ) + conv_name = _init_converter_pat % (a.name,) + names_for_globals[conv_name] = a.converter + else: + lines.append( + fmt_setter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, + ) + ) + elif a.default is not NOTHING and not has_factory: + arg = f"{arg_name}=attr_dict['{attr_name}'].default" + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + elif has_factory: + arg = f"{arg_name}=NOTHING" + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + lines.append(f"if {arg_name} is not NOTHING:") + + init_factory_name = _init_factory_pat % (a.name,) + if a.converter is not None: + lines.append( + " " + + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter_with_converter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append( + " " + fmt_setter(attr_name, arg_name, has_on_setattr) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.kw_only: + kw_only_args.append(arg_name) + else: + args.append(arg_name) + + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + if a.init is True: + if a.type is not None and a.converter is None: + annotations[arg_name] = a.type + elif a.converter is not None: + # Try to get the type from the converter. + t = _AnnotationExtractor(a.converter).get_first_param_type() + if t: + annotations[arg_name] = t + + if attrs_to_validate: # we can skip this if there are no validators. + names_for_globals["_config"] = _config + lines.append("if _config._run_validators is True:") + for a in attrs_to_validate: + val_name = "__attr_validator_" + a.name + attr_name = "__attr_" + a.name + lines.append(f" {val_name}(self, {attr_name}, self.{a.name})") + names_for_globals[val_name] = a.validator + names_for_globals[attr_name] = a + + if post_init: + lines.append("self.__attrs_post_init__()") + + # because this is set only after __attrs_post_init__ is called, a crash + # will result if post-init tries to access the hash code. This seemed + # preferable to setting this beforehand, in which case alteration to + # field values during post-init combined with post-init accessing the + # hash code would result in silent bugs. + if cache_hash: + if frozen: + if slots: + # if frozen and slots, then _setattr defined above + init_hash_cache = "_setattr('%s', %s)" + else: + # if frozen and not slots, then _inst_dict defined above + init_hash_cache = "_inst_dict['%s'] = %s" + else: + init_hash_cache = "self.%s = %s" + lines.append(init_hash_cache % (_hash_cache_field, "None")) + + # For exceptions we rely on BaseException.__init__ for proper + # initialization. + if is_exc: + vals = ",".join(f"self.{a.name}" for a in attrs if a.init) + + lines.append(f"BaseException.__init__(self, {vals})") + + args = ", ".join(args) + if kw_only_args: + args += "%s*, %s" % ( + ", " if args else "", # leading comma + ", ".join(kw_only_args), # kw_only args + ) + + return ( + "def %s(self, %s):\n %s\n" + % ( + ("__attrs_init__" if attrs_init else "__init__"), + args, + "\n ".join(lines) if lines else "pass", + ), + names_for_globals, + annotations, + ) + + +def _default_init_alias_for(name: str) -> str: + """ + The default __init__ parameter name for a field. + + This performs private-name adjustment via leading-unscore stripping, + and is the default value of Attribute.alias if not provided. + """ + + return name.lstrip("_") + + +class Attribute: + """ + *Read-only* representation of an attribute. + + The class has *all* arguments of `attr.ib` (except for ``factory`` + which is only syntactic sugar for ``default=Factory(...)`` plus the + following: + + - ``name`` (`str`): The name of the attribute. + - ``alias`` (`str`): The __init__ parameter name of the attribute, after + any explicit overrides and default private-attribute-name handling. + - ``inherited`` (`bool`): Whether or not that attribute has been inherited + from a base class. + - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The callables + that are used for comparing and ordering objects by this attribute, + respectively. These are set by passing a callable to `attr.ib`'s ``eq``, + ``order``, or ``cmp`` arguments. See also :ref:`comparison customization + `. + + Instances of this class are frequently used for introspection purposes + like: + + - `fields` returns a tuple of them. + - Validators get them passed as the first argument. + - The :ref:`field transformer ` hook receives a list of + them. + - The ``alias`` property exposes the __init__ parameter name of the field, + with any overrides and default private-attribute handling applied. + + + .. versionadded:: 20.1.0 *inherited* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.2.0 *inherited* is not taken into account for + equality checks and hashing anymore. + .. versionadded:: 21.1.0 *eq_key* and *order_key* + .. versionadded:: 22.2.0 *alias* + + For the full version history of the fields, see `attr.ib`. + """ + + __slots__ = ( + "name", + "default", + "validator", + "repr", + "eq", + "eq_key", + "order", + "order_key", + "hash", + "init", + "metadata", + "type", + "converter", + "kw_only", + "inherited", + "on_setattr", + "alias", + ) + + def __init__( + self, + name, + default, + validator, + repr, + cmp, # XXX: unused, remove along with other cmp code. + hash, + init, + inherited, + metadata=None, + type=None, + converter=None, + kw_only=False, + eq=None, + eq_key=None, + order=None, + order_key=None, + on_setattr=None, + alias=None, + ): + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq_key or eq, order_key or order, True + ) + + # Cache this descriptor here to speed things up later. + bound_setattr = _obj_setattr.__get__(self) + + # Despite the big red warning, people *do* instantiate `Attribute` + # themselves. + bound_setattr("name", name) + bound_setattr("default", default) + bound_setattr("validator", validator) + bound_setattr("repr", repr) + bound_setattr("eq", eq) + bound_setattr("eq_key", eq_key) + bound_setattr("order", order) + bound_setattr("order_key", order_key) + bound_setattr("hash", hash) + bound_setattr("init", init) + bound_setattr("converter", converter) + bound_setattr( + "metadata", + ( + types.MappingProxyType(dict(metadata)) # Shallow copy + if metadata + else _empty_metadata_singleton + ), + ) + bound_setattr("type", type) + bound_setattr("kw_only", kw_only) + bound_setattr("inherited", inherited) + bound_setattr("on_setattr", on_setattr) + bound_setattr("alias", alias) + + def __setattr__(self, name, value): + raise FrozenInstanceError() + + @classmethod + def from_counting_attr(cls, name, ca, type=None): + # type holds the annotated value. deal with conflicts: + if type is None: + type = ca.type + elif ca.type is not None: + raise ValueError( + "Type annotation and type argument cannot both be present" + ) + inst_dict = { + k: getattr(ca, k) + for k in Attribute.__slots__ + if k + not in ( + "name", + "validator", + "default", + "type", + "inherited", + ) # exclude methods and deprecated alias + } + return cls( + name=name, + validator=ca._validator, + default=ca._default, + type=type, + cmp=None, + inherited=False, + **inst_dict, + ) + + # Don't use attr.evolve since fields(Attribute) doesn't work + def evolve(self, **changes): + """ + Copy *self* and apply *changes*. + + This works similarly to `attr.evolve` but that function does not work + with ``Attribute``. + + It is mainly meant to be used for `transform-fields`. + + .. versionadded:: 20.3.0 + """ + new = copy.copy(self) + + new._setattrs(changes.items()) + + return new + + # Don't use _add_pickle since fields(Attribute) doesn't work + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple( + getattr(self, name) if name != "metadata" else dict(self.metadata) + for name in self.__slots__ + ) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + self._setattrs(zip(self.__slots__, state)) + + def _setattrs(self, name_values_pairs): + bound_setattr = _obj_setattr.__get__(self) + for name, value in name_values_pairs: + if name != "metadata": + bound_setattr(name, value) + else: + bound_setattr( + name, + types.MappingProxyType(dict(value)) + if value + else _empty_metadata_singleton, + ) + + +_a = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=(name != "metadata"), + init=True, + inherited=False, + alias=_default_init_alias_for(name), + ) + for name in Attribute.__slots__ +] + +Attribute = _add_hash( + _add_eq( + _add_repr(Attribute, attrs=_a), + attrs=[a for a in _a if a.name != "inherited"], + ), + attrs=[a for a in _a if a.hash and a.name != "inherited"], +) + + +class _CountingAttr: + """ + Intermediate representation of attributes that uses a counter to preserve + the order in which the attributes have been defined. + + *Internal* data structure of the attrs library. Running into is most + likely the result of a bug like a forgotten `@attr.s` decorator. + """ + + __slots__ = ( + "counter", + "_default", + "repr", + "eq", + "eq_key", + "order", + "order_key", + "hash", + "init", + "metadata", + "_validator", + "converter", + "type", + "kw_only", + "on_setattr", + "alias", + ) + __attrs_attrs__ = tuple( + Attribute( + name=name, + alias=_default_init_alias_for(name), + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=True, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "on_setattr", + "alias", + ) + ) + ( + Attribute( + name="metadata", + alias="metadata", + default=None, + validator=None, + repr=True, + cmp=None, + hash=False, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ), + ) + cls_counter = 0 + + def __init__( + self, + default, + validator, + repr, + cmp, + hash, + init, + converter, + metadata, + type, + kw_only, + eq, + eq_key, + order, + order_key, + on_setattr, + alias, + ): + _CountingAttr.cls_counter += 1 + self.counter = _CountingAttr.cls_counter + self._default = default + self._validator = validator + self.converter = converter + self.repr = repr + self.eq = eq + self.eq_key = eq_key + self.order = order + self.order_key = order_key + self.hash = hash + self.init = init + self.metadata = metadata + self.type = type + self.kw_only = kw_only + self.on_setattr = on_setattr + self.alias = alias + + def validator(self, meth): + """ + Decorator that adds *meth* to the list of validators. + + Returns *meth* unchanged. + + .. versionadded:: 17.1.0 + """ + if self._validator is None: + self._validator = meth + else: + self._validator = and_(self._validator, meth) + return meth + + def default(self, meth): + """ + Decorator that allows to set the default for an attribute. + + Returns *meth* unchanged. + + :raises DefaultAlreadySetError: If default has been set before. + + .. versionadded:: 17.1.0 + """ + if self._default is not NOTHING: + raise DefaultAlreadySetError() + + self._default = Factory(meth, takes_self=True) + + return meth + + +_CountingAttr = _add_eq(_add_repr(_CountingAttr)) + + +class Factory: + """ + Stores a factory callable. + + If passed as the default value to `attrs.field`, the factory is used to + generate a new value. + + :param callable factory: A callable that takes either none or exactly one + mandatory positional argument depending on *takes_self*. + :param bool takes_self: Pass the partially initialized instance that is + being initialized as a positional argument. + + .. versionadded:: 17.1.0 *takes_self* + """ + + __slots__ = ("factory", "takes_self") + + def __init__(self, factory, takes_self=False): + """ + `Factory` is part of the default machinery so if we want a default + value here, we have to implement it ourselves. + """ + self.factory = factory + self.takes_self = takes_self + + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + for name, value in zip(self.__slots__, state): + setattr(self, name, value) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in Factory.__slots__ +] + +Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) + + +def make_class(name, attrs, bases=(object,), **attributes_arguments): + """ + A quick way to create a new class called *name* with *attrs*. + + :param str name: The name for the new class. + + :param attrs: A list of names or a dictionary of mappings of names to + attributes. + + The order is deduced from the order of the names or attributes inside + *attrs*. Otherwise the order of the definition of the attributes is + used. + :type attrs: `list` or `dict` + + :param tuple bases: Classes that the new class will subclass. + + :param attributes_arguments: Passed unmodified to `attr.s`. + + :return: A new class with *attrs*. + :rtype: type + + .. versionadded:: 17.1.0 *bases* + .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + """ + if isinstance(attrs, dict): + cls_dict = attrs + elif isinstance(attrs, (list, tuple)): + cls_dict = {a: attrib() for a in attrs} + else: + raise TypeError("attrs argument must be a dict or a list.") + + pre_init = cls_dict.pop("__attrs_pre_init__", None) + post_init = cls_dict.pop("__attrs_post_init__", None) + user_init = cls_dict.pop("__init__", None) + + body = {} + if pre_init is not None: + body["__attrs_pre_init__"] = pre_init + if post_init is not None: + body["__attrs_post_init__"] = post_init + if user_init is not None: + body["__init__"] = user_init + + type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) + + # For pickling to work, the __module__ variable needs to be set to the + # frame where the class is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython). + try: + type_.__module__ = sys._getframe(1).f_globals.get( + "__name__", "__main__" + ) + except (AttributeError, ValueError): + pass + + # We do it here for proper warnings with meaningful stacklevel. + cmp = attributes_arguments.pop("cmp", None) + ( + attributes_arguments["eq"], + attributes_arguments["order"], + ) = _determine_attrs_eq_order( + cmp, + attributes_arguments.get("eq"), + attributes_arguments.get("order"), + True, + ) + + return _attrs(these=cls_dict, **attributes_arguments)(type_) + + +# These are required by within this module so we define them here and merely +# import into .validators / .converters. + + +@attrs(slots=True, hash=True) +class _AndValidator: + """ + Compose many validators to a single one. + """ + + _validators = attrib() + + def __call__(self, inst, attr, value): + for v in self._validators: + v(inst, attr, value) + + +def and_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators. + + :param callables validators: Arbitrary number of validators. + + .. versionadded:: 17.1.0 + """ + vals = [] + for validator in validators: + vals.extend( + validator._validators + if isinstance(validator, _AndValidator) + else [validator] + ) + + return _AndValidator(tuple(vals)) + + +def pipe(*converters): + """ + A converter that composes multiple converters into one. + + When called on a value, it runs all wrapped converters, returning the + *last* value. + + Type annotations will be inferred from the wrapped converters', if + they have any. + + :param callables converters: Arbitrary number of converters. + + .. versionadded:: 20.1.0 + """ + + def pipe_converter(val): + for converter in converters: + val = converter(val) + + return val + + if not converters: + # If the converter list is empty, pipe_converter is the identity. + A = typing.TypeVar("A") + pipe_converter.__annotations__ = {"val": A, "return": A} + else: + # Get parameter type from first converter. + t = _AnnotationExtractor(converters[0]).get_first_param_type() + if t: + pipe_converter.__annotations__["val"] = t + + # Get return type from last converter. + rt = _AnnotationExtractor(converters[-1]).get_return_type() + if rt: + pipe_converter.__annotations__["return"] = rt + + return pipe_converter diff --git a/lib/attr/_next_gen.py b/lib/attr/_next_gen.py new file mode 100644 index 0000000..c59d848 --- /dev/null +++ b/lib/attr/_next_gen.py @@ -0,0 +1,226 @@ +# SPDX-License-Identifier: MIT + +""" +These are keyword-only APIs that call `attr.s` and `attr.ib` with different +default values. +""" + + +from functools import partial + +from . import setters +from ._funcs import asdict as _asdict +from ._funcs import astuple as _astuple +from ._make import ( + NOTHING, + _frozen_setattrs, + _ng_default_on_setattr, + attrib, + attrs, +) +from .exceptions import UnannotatedAttributeError + + +def define( + maybe_cls=None, + *, + these=None, + repr=None, + unsafe_hash=None, + hash=None, + init=None, + slots=True, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=None, + kw_only=False, + cache_hash=False, + auto_exc=True, + eq=None, + order=False, + auto_detect=True, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, +): + r""" + Define an ``attrs`` class. + + Differences to the classic `attr.s` that it uses underneath: + + - Automatically detect whether or not *auto_attribs* should be `True` (c.f. + *auto_attribs* parameter). + - If *frozen* is `False`, run converters and validators when setting an + attribute by default. + - *slots=True* + + .. caution:: + + Usually this has only upsides and few visible effects in everyday + programming. But it *can* lead to some suprising behaviors, so please + make sure to read :term:`slotted classes`. + - *auto_exc=True* + - *auto_detect=True* + - *order=False* + - Some options that were only relevant on Python 2 or were kept around for + backwards-compatibility have been removed. + + Please note that these are all defaults and you can change them as you + wish. + + :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves + exactly like `attr.s`. If left `None`, `attr.s` will try to guess: + + 1. If any attributes are annotated and no unannotated `attrs.fields`\ s + are found, it assumes *auto_attribs=True*. + 2. Otherwise it assumes *auto_attribs=False* and tries to collect + `attrs.fields`\ s. + + For now, please refer to `attr.s` for the rest of the parameters. + + .. versionadded:: 20.1.0 + .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + """ + + def do_it(cls, auto_attribs): + return attrs( + maybe_cls=cls, + these=these, + repr=repr, + hash=hash, + unsafe_hash=unsafe_hash, + init=init, + slots=slots, + frozen=frozen, + weakref_slot=weakref_slot, + str=str, + auto_attribs=auto_attribs, + kw_only=kw_only, + cache_hash=cache_hash, + auto_exc=auto_exc, + eq=eq, + order=order, + auto_detect=auto_detect, + collect_by_mro=True, + getstate_setstate=getstate_setstate, + on_setattr=on_setattr, + field_transformer=field_transformer, + match_args=match_args, + ) + + def wrap(cls): + """ + Making this a wrapper ensures this code runs during class creation. + + We also ensure that frozen-ness of classes is inherited. + """ + nonlocal frozen, on_setattr + + had_on_setattr = on_setattr not in (None, setters.NO_OP) + + # By default, mutable classes convert & validate on setattr. + if frozen is False and on_setattr is None: + on_setattr = _ng_default_on_setattr + + # However, if we subclass a frozen class, we inherit the immutability + # and disable on_setattr. + for base_cls in cls.__bases__: + if base_cls.__setattr__ is _frozen_setattrs: + if had_on_setattr: + raise ValueError( + "Frozen classes can't use on_setattr " + "(frozen-ness was inherited)." + ) + + on_setattr = setters.NO_OP + break + + if auto_attribs is not None: + return do_it(cls, auto_attribs) + + try: + return do_it(cls, True) + except UnannotatedAttributeError: + return do_it(cls, False) + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but ``None`` if used as `@attrs()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +mutable = define +frozen = partial(define, frozen=True, on_setattr=None) + + +def field( + *, + default=NOTHING, + validator=None, + repr=True, + hash=None, + init=True, + metadata=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, + alias=None, +): + """ + Identical to `attr.ib`, except keyword-only and with some arguments + removed. + + .. versionadded:: 20.1.0 + """ + return attrib( + default=default, + validator=validator, + repr=repr, + hash=hash, + init=init, + metadata=metadata, + converter=converter, + factory=factory, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + alias=alias, + ) + + +def asdict(inst, *, recurse=True, filter=None, value_serializer=None): + """ + Same as `attr.asdict`, except that collections types are always retained + and dict is always used as *dict_factory*. + + .. versionadded:: 21.3.0 + """ + return _asdict( + inst=inst, + recurse=recurse, + filter=filter, + value_serializer=value_serializer, + retain_collection_types=True, + ) + + +def astuple(inst, *, recurse=True, filter=None): + """ + Same as `attr.astuple`, except that collections types are always retained + and `tuple` is always used as the *tuple_factory*. + + .. versionadded:: 21.3.0 + """ + return _astuple( + inst=inst, recurse=recurse, filter=filter, retain_collection_types=True + ) diff --git a/lib/attr/_typing_compat.pyi b/lib/attr/_typing_compat.pyi new file mode 100644 index 0000000..ca7b71e --- /dev/null +++ b/lib/attr/_typing_compat.pyi @@ -0,0 +1,15 @@ +from typing import Any, ClassVar, Protocol + +# MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`. +MYPY = False + +if MYPY: + # A protocol to be able to statically accept an attrs class. + class AttrsInstance_(Protocol): + __attrs_attrs__: ClassVar[Any] + +else: + # For type checkers without plug-in support use an empty protocol that + # will (hopefully) be combined into a union. + class AttrsInstance_(Protocol): + pass diff --git a/lib/attr/_version_info.py b/lib/attr/_version_info.py new file mode 100644 index 0000000..51a1312 --- /dev/null +++ b/lib/attr/_version_info.py @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: MIT + + +from functools import total_ordering + +from ._funcs import astuple +from ._make import attrib, attrs + + +@total_ordering +@attrs(eq=False, order=False, slots=True, frozen=True) +class VersionInfo: + """ + A version object that can be compared to tuple of length 1--4: + + >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) + True + >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) + True + >>> vi = attr.VersionInfo(19, 2, 0, "final") + >>> vi < (19, 1, 1) + False + >>> vi < (19,) + False + >>> vi == (19, 2,) + True + >>> vi == (19, 2, 1) + False + + .. versionadded:: 19.2 + """ + + year = attrib(type=int) + minor = attrib(type=int) + micro = attrib(type=int) + releaselevel = attrib(type=str) + + @classmethod + def _from_version_string(cls, s): + """ + Parse *s* and return a _VersionInfo. + """ + v = s.split(".") + if len(v) == 3: + v.append("final") + + return cls( + year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] + ) + + def _ensure_tuple(self, other): + """ + Ensure *other* is a tuple of a valid length. + + Returns a possibly transformed *other* and ourselves as a tuple of + the same length as *other*. + """ + + if self.__class__ is other.__class__: + other = astuple(other) + + if not isinstance(other, tuple): + raise NotImplementedError + + if not (1 <= len(other) <= 4): + raise NotImplementedError + + return astuple(self)[: len(other)], other + + def __eq__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + return us == them + + def __lt__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't + # have to do anything special with releaselevel for now. + return us < them diff --git a/lib/attr/_version_info.pyi b/lib/attr/_version_info.pyi new file mode 100644 index 0000000..45ced08 --- /dev/null +++ b/lib/attr/_version_info.pyi @@ -0,0 +1,9 @@ +class VersionInfo: + @property + def year(self) -> int: ... + @property + def minor(self) -> int: ... + @property + def micro(self) -> int: ... + @property + def releaselevel(self) -> str: ... diff --git a/lib/attr/converters.py b/lib/attr/converters.py new file mode 100644 index 0000000..4cada10 --- /dev/null +++ b/lib/attr/converters.py @@ -0,0 +1,144 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful converters. +""" + + +import typing + +from ._compat import _AnnotationExtractor +from ._make import NOTHING, Factory, pipe + + +__all__ = [ + "default_if_none", + "optional", + "pipe", + "to_bool", +] + + +def optional(converter): + """ + A converter that allows an attribute to be optional. An optional attribute + is one which can be set to ``None``. + + Type annotations will be inferred from the wrapped converter's, if it + has any. + + :param callable converter: the converter that is used for non-``None`` + values. + + .. versionadded:: 17.1.0 + """ + + def optional_converter(val): + if val is None: + return None + return converter(val) + + xtr = _AnnotationExtractor(converter) + + t = xtr.get_first_param_type() + if t: + optional_converter.__annotations__["val"] = typing.Optional[t] + + rt = xtr.get_return_type() + if rt: + optional_converter.__annotations__["return"] = typing.Optional[rt] + + return optional_converter + + +def default_if_none(default=NOTHING, factory=None): + """ + A converter that allows to replace ``None`` values by *default* or the + result of *factory*. + + :param default: Value to be used if ``None`` is passed. Passing an instance + of `attrs.Factory` is supported, however the ``takes_self`` option + is *not*. + :param callable factory: A callable that takes no parameters whose result + is used if ``None`` is passed. + + :raises TypeError: If **neither** *default* or *factory* is passed. + :raises TypeError: If **both** *default* and *factory* are passed. + :raises ValueError: If an instance of `attrs.Factory` is passed with + ``takes_self=True``. + + .. versionadded:: 18.2.0 + """ + if default is NOTHING and factory is None: + raise TypeError("Must pass either `default` or `factory`.") + + if default is not NOTHING and factory is not None: + raise TypeError( + "Must pass either `default` or `factory` but not both." + ) + + if factory is not None: + default = Factory(factory) + + if isinstance(default, Factory): + if default.takes_self: + raise ValueError( + "`takes_self` is not supported by default_if_none." + ) + + def default_if_none_converter(val): + if val is not None: + return val + + return default.factory() + + else: + + def default_if_none_converter(val): + if val is not None: + return val + + return default + + return default_if_none_converter + + +def to_bool(val): + """ + Convert "boolean" strings (e.g., from env. vars.) to real booleans. + + Values mapping to :code:`True`: + + - :code:`True` + - :code:`"true"` / :code:`"t"` + - :code:`"yes"` / :code:`"y"` + - :code:`"on"` + - :code:`"1"` + - :code:`1` + + Values mapping to :code:`False`: + + - :code:`False` + - :code:`"false"` / :code:`"f"` + - :code:`"no"` / :code:`"n"` + - :code:`"off"` + - :code:`"0"` + - :code:`0` + + :raises ValueError: for any other value. + + .. versionadded:: 21.3.0 + """ + if isinstance(val, str): + val = val.lower() + truthy = {True, "true", "t", "yes", "y", "on", "1", 1} + falsy = {False, "false", "f", "no", "n", "off", "0", 0} + try: + if val in truthy: + return True + if val in falsy: + return False + except TypeError: + # Raised when "val" is not hashable (e.g., lists) + pass + raise ValueError(f"Cannot convert value to bool: {val}") diff --git a/lib/attr/converters.pyi b/lib/attr/converters.pyi new file mode 100644 index 0000000..5abb49f --- /dev/null +++ b/lib/attr/converters.pyi @@ -0,0 +1,13 @@ +from typing import Callable, TypeVar, overload + +from . import _ConverterType + +_T = TypeVar("_T") + +def pipe(*validators: _ConverterType) -> _ConverterType: ... +def optional(converter: _ConverterType) -> _ConverterType: ... +@overload +def default_if_none(default: _T) -> _ConverterType: ... +@overload +def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ... +def to_bool(val: str) -> bool: ... diff --git a/lib/attr/exceptions.py b/lib/attr/exceptions.py new file mode 100644 index 0000000..5dc51e0 --- /dev/null +++ b/lib/attr/exceptions.py @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: MIT + + +class FrozenError(AttributeError): + """ + A frozen/immutable instance or attribute have been attempted to be + modified. + + It mirrors the behavior of ``namedtuples`` by using the same error message + and subclassing `AttributeError`. + + .. versionadded:: 20.1.0 + """ + + msg = "can't set attribute" + args = [msg] + + +class FrozenInstanceError(FrozenError): + """ + A frozen instance has been attempted to be modified. + + .. versionadded:: 16.1.0 + """ + + +class FrozenAttributeError(FrozenError): + """ + A frozen attribute has been attempted to be modified. + + .. versionadded:: 20.1.0 + """ + + +class AttrsAttributeNotFoundError(ValueError): + """ + An ``attrs`` function couldn't find an attribute that the user asked for. + + .. versionadded:: 16.2.0 + """ + + +class NotAnAttrsClassError(ValueError): + """ + A non-``attrs`` class has been passed into an ``attrs`` function. + + .. versionadded:: 16.2.0 + """ + + +class DefaultAlreadySetError(RuntimeError): + """ + A default has been set using ``attr.ib()`` and is attempted to be reset + using the decorator. + + .. versionadded:: 17.1.0 + """ + + +class UnannotatedAttributeError(RuntimeError): + """ + A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type + annotation. + + .. versionadded:: 17.3.0 + """ + + +class PythonTooOldError(RuntimeError): + """ + It was attempted to use an ``attrs`` feature that requires a newer Python + version. + + .. versionadded:: 18.2.0 + """ + + +class NotCallableError(TypeError): + """ + A ``attr.ib()`` requiring a callable has been set with a value + that is not callable. + + .. versionadded:: 19.2.0 + """ + + def __init__(self, msg, value): + super(TypeError, self).__init__(msg, value) + self.msg = msg + self.value = value + + def __str__(self): + return str(self.msg) diff --git a/lib/attr/exceptions.pyi b/lib/attr/exceptions.pyi new file mode 100644 index 0000000..f268011 --- /dev/null +++ b/lib/attr/exceptions.pyi @@ -0,0 +1,17 @@ +from typing import Any + +class FrozenError(AttributeError): + msg: str = ... + +class FrozenInstanceError(FrozenError): ... +class FrozenAttributeError(FrozenError): ... +class AttrsAttributeNotFoundError(ValueError): ... +class NotAnAttrsClassError(ValueError): ... +class DefaultAlreadySetError(RuntimeError): ... +class UnannotatedAttributeError(RuntimeError): ... +class PythonTooOldError(RuntimeError): ... + +class NotCallableError(TypeError): + msg: str = ... + value: Any = ... + def __init__(self, msg: str, value: Any) -> None: ... diff --git a/lib/attr/filters.py b/lib/attr/filters.py new file mode 100644 index 0000000..baa25e9 --- /dev/null +++ b/lib/attr/filters.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful filters for `attr.asdict`. +""" + +from ._make import Attribute + + +def _split_what(what): + """ + Returns a tuple of `frozenset`s of classes and attributes. + """ + return ( + frozenset(cls for cls in what if isinstance(cls, type)), + frozenset(cls for cls in what if isinstance(cls, Attribute)), + ) + + +def include(*what): + """ + Include *what*. + + :param what: What to include. + :type what: `list` of `type` or `attrs.Attribute`\\ s + + :rtype: `callable` + """ + cls, attrs = _split_what(what) + + def include_(attribute, value): + return value.__class__ in cls or attribute in attrs + + return include_ + + +def exclude(*what): + """ + Exclude *what*. + + :param what: What to exclude. + :type what: `list` of classes or `attrs.Attribute`\\ s. + + :rtype: `callable` + """ + cls, attrs = _split_what(what) + + def exclude_(attribute, value): + return value.__class__ not in cls and attribute not in attrs + + return exclude_ diff --git a/lib/attr/filters.pyi b/lib/attr/filters.pyi new file mode 100644 index 0000000..9938668 --- /dev/null +++ b/lib/attr/filters.pyi @@ -0,0 +1,6 @@ +from typing import Any, Union + +from . import Attribute, _FilterType + +def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... +def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... diff --git a/lib/attr/py.typed b/lib/attr/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lib/attr/setters.py b/lib/attr/setters.py new file mode 100644 index 0000000..12ed675 --- /dev/null +++ b/lib/attr/setters.py @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly used hooks for on_setattr. +""" + + +from . import _config +from .exceptions import FrozenAttributeError + + +def pipe(*setters): + """ + Run all *setters* and return the return value of the last one. + + .. versionadded:: 20.1.0 + """ + + def wrapped_pipe(instance, attrib, new_value): + rv = new_value + + for setter in setters: + rv = setter(instance, attrib, rv) + + return rv + + return wrapped_pipe + + +def frozen(_, __, ___): + """ + Prevent an attribute to be modified. + + .. versionadded:: 20.1.0 + """ + raise FrozenAttributeError() + + +def validate(instance, attrib, new_value): + """ + Run *attrib*'s validator on *new_value* if it has one. + + .. versionadded:: 20.1.0 + """ + if _config._run_validators is False: + return new_value + + v = attrib.validator + if not v: + return new_value + + v(instance, attrib, new_value) + + return new_value + + +def convert(instance, attrib, new_value): + """ + Run *attrib*'s converter -- if it has one -- on *new_value* and return the + result. + + .. versionadded:: 20.1.0 + """ + c = attrib.converter + if c: + return c(new_value) + + return new_value + + +# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. +# autodata stopped working, so the docstring is inlined in the API docs. +NO_OP = object() diff --git a/lib/attr/setters.pyi b/lib/attr/setters.pyi new file mode 100644 index 0000000..72f7ce4 --- /dev/null +++ b/lib/attr/setters.pyi @@ -0,0 +1,19 @@ +from typing import Any, NewType, NoReturn, TypeVar + +from . import Attribute, _OnSetAttrType + +_T = TypeVar("_T") + +def frozen( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> NoReturn: ... +def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... +def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... + +# convert is allowed to return Any, because they can be chained using pipe. +def convert( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> Any: ... + +_NoOpType = NewType("_NoOpType", object) +NO_OP: _NoOpType diff --git a/lib/attr/validators.py b/lib/attr/validators.py new file mode 100644 index 0000000..852ae96 --- /dev/null +++ b/lib/attr/validators.py @@ -0,0 +1,714 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful validators. +""" + + +import operator +import re + +from contextlib import contextmanager + +from ._config import get_run_validators, set_run_validators +from ._make import _AndValidator, and_, attrib, attrs +from .converters import default_if_none +from .exceptions import NotCallableError + + +try: + Pattern = re.Pattern +except AttributeError: # Python <3.7 lacks a Pattern type. + Pattern = type(re.compile("")) + + +__all__ = [ + "and_", + "deep_iterable", + "deep_mapping", + "disabled", + "ge", + "get_disabled", + "gt", + "in_", + "instance_of", + "is_callable", + "le", + "lt", + "matches_re", + "max_len", + "min_len", + "not_", + "optional", + "provides", + "set_disabled", +] + + +def set_disabled(disabled): + """ + Globally disable or enable running validators. + + By default, they are run. + + :param disabled: If ``True``, disable running all validators. + :type disabled: bool + + .. warning:: + + This function is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(not disabled) + + +def get_disabled(): + """ + Return a bool indicating whether validators are currently disabled or not. + + :return: ``True`` if validators are currently disabled. + :rtype: bool + + .. versionadded:: 21.3.0 + """ + return not get_run_validators() + + +@contextmanager +def disabled(): + """ + Context manager that disables running validators within its context. + + .. warning:: + + This context manager is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(False) + try: + yield + finally: + set_run_validators(True) + + +@attrs(repr=False, slots=True, hash=True) +class _InstanceOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not isinstance(value, self.type): + raise TypeError( + "'{name}' must be {type!r} (got {value!r} that is a " + "{actual!r}).".format( + name=attr.name, + type=self.type, + actual=value.__class__, + value=value, + ), + attr, + self.type, + value, + ) + + def __repr__(self): + return "".format( + type=self.type + ) + + +def instance_of(type): + """ + A validator that raises a `TypeError` if the initializer is called + with a wrong type for this particular attribute (checks are performed using + `isinstance` therefore it's also valid to pass a tuple of types). + + :param type: The type to check for. + :type type: type or tuple of type + + :raises TypeError: With a human readable error message, the attribute + (of type `attrs.Attribute`), the expected type, and the value it + got. + """ + return _InstanceOfValidator(type) + + +@attrs(repr=False, frozen=True, slots=True) +class _MatchesReValidator: + pattern = attrib() + match_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.match_func(value): + raise ValueError( + "'{name}' must match regex {pattern!r}" + " ({value!r} doesn't)".format( + name=attr.name, pattern=self.pattern.pattern, value=value + ), + attr, + self.pattern, + value, + ) + + def __repr__(self): + return "".format( + pattern=self.pattern + ) + + +def matches_re(regex, flags=0, func=None): + r""" + A validator that raises `ValueError` if the initializer is called + with a string that doesn't match *regex*. + + :param regex: a regex string or precompiled pattern to match against + :param int flags: flags that will be passed to the underlying re function + (default 0) + :param callable func: which underlying `re` function to call. Valid options + are `re.fullmatch`, `re.search`, and `re.match`; the default ``None`` + means `re.fullmatch`. For performance reasons, the pattern is always + precompiled using `re.compile`. + + .. versionadded:: 19.2.0 + .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. + """ + valid_funcs = (re.fullmatch, None, re.search, re.match) + if func not in valid_funcs: + raise ValueError( + "'func' must be one of {}.".format( + ", ".join( + sorted( + e and e.__name__ or "None" for e in set(valid_funcs) + ) + ) + ) + ) + + if isinstance(regex, Pattern): + if flags: + raise TypeError( + "'flags' can only be used with a string pattern; " + "pass flags to re.compile() instead" + ) + pattern = regex + else: + pattern = re.compile(regex, flags) + + if func is re.match: + match_func = pattern.match + elif func is re.search: + match_func = pattern.search + else: + match_func = pattern.fullmatch + + return _MatchesReValidator(pattern, match_func) + + +@attrs(repr=False, slots=True, hash=True) +class _ProvidesValidator: + interface = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.interface.providedBy(value): + raise TypeError( + "'{name}' must provide {interface!r} which {value!r} " + "doesn't.".format( + name=attr.name, interface=self.interface, value=value + ), + attr, + self.interface, + value, + ) + + def __repr__(self): + return "".format( + interface=self.interface + ) + + +def provides(interface): + """ + A validator that raises a `TypeError` if the initializer is called + with an object that does not provide the requested *interface* (checks are + performed using ``interface.providedBy(value)`` (see `zope.interface + `_). + + :param interface: The interface to check for. + :type interface: ``zope.interface.Interface`` + + :raises TypeError: With a human readable error message, the attribute + (of type `attrs.Attribute`), the expected interface, and the + value it got. + """ + return _ProvidesValidator(interface) + + +@attrs(repr=False, slots=True, hash=True) +class _OptionalValidator: + validator = attrib() + + def __call__(self, inst, attr, value): + if value is None: + return + + self.validator(inst, attr, value) + + def __repr__(self): + return "".format( + what=repr(self.validator) + ) + + +def optional(validator): + """ + A validator that makes an attribute optional. An optional attribute is one + which can be set to ``None`` in addition to satisfying the requirements of + the sub-validator. + + :param validator: A validator (or a list of validators) that is used for + non-``None`` values. + :type validator: callable or `list` of callables. + + .. versionadded:: 15.1.0 + .. versionchanged:: 17.1.0 *validator* can be a list of validators. + """ + if isinstance(validator, list): + return _OptionalValidator(_AndValidator(validator)) + return _OptionalValidator(validator) + + +@attrs(repr=False, slots=True, hash=True) +class _InValidator: + options = attrib() + + def __call__(self, inst, attr, value): + try: + in_options = value in self.options + except TypeError: # e.g. `1 in "abc"` + in_options = False + + if not in_options: + raise ValueError( + "'{name}' must be in {options!r} (got {value!r})".format( + name=attr.name, options=self.options, value=value + ), + attr, + self.options, + value, + ) + + def __repr__(self): + return "".format( + options=self.options + ) + + +def in_(options): + """ + A validator that raises a `ValueError` if the initializer is called + with a value that does not belong in the options provided. The check is + performed using ``value in options``. + + :param options: Allowed options. + :type options: list, tuple, `enum.Enum`, ... + + :raises ValueError: With a human readable error message, the attribute (of + type `attrs.Attribute`), the expected options, and the value it + got. + + .. versionadded:: 17.1.0 + .. versionchanged:: 22.1.0 + The ValueError was incomplete until now and only contained the human + readable error message. Now it contains all the information that has + been promised since 17.1.0. + """ + return _InValidator(options) + + +@attrs(repr=False, slots=False, hash=True) +class _IsCallableValidator: + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not callable(value): + message = ( + "'{name}' must be callable " + "(got {value!r} that is a {actual!r})." + ) + raise NotCallableError( + msg=message.format( + name=attr.name, value=value, actual=value.__class__ + ), + value=value, + ) + + def __repr__(self): + return "" + + +def is_callable(): + """ + A validator that raises a `attr.exceptions.NotCallableError` if the + initializer is called with a value for this particular attribute + that is not callable. + + .. versionadded:: 19.1.0 + + :raises `attr.exceptions.NotCallableError`: With a human readable error + message containing the attribute (`attrs.Attribute`) name, + and the value it got. + """ + return _IsCallableValidator() + + +@attrs(repr=False, slots=True, hash=True) +class _DeepIterable: + member_validator = attrib(validator=is_callable()) + iterable_validator = attrib( + default=None, validator=optional(is_callable()) + ) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.iterable_validator is not None: + self.iterable_validator(inst, attr, value) + + for member in value: + self.member_validator(inst, attr, member) + + def __repr__(self): + iterable_identifier = ( + "" + if self.iterable_validator is None + else f" {self.iterable_validator!r}" + ) + return ( + "" + ).format( + iterable_identifier=iterable_identifier, + member=self.member_validator, + ) + + +def deep_iterable(member_validator, iterable_validator=None): + """ + A validator that performs deep validation of an iterable. + + :param member_validator: Validator(s) to apply to iterable members + :param iterable_validator: Validator to apply to iterable itself + (optional) + + .. versionadded:: 19.1.0 + + :raises TypeError: if any sub-validators fail + """ + if isinstance(member_validator, (list, tuple)): + member_validator = and_(*member_validator) + return _DeepIterable(member_validator, iterable_validator) + + +@attrs(repr=False, slots=True, hash=True) +class _DeepMapping: + key_validator = attrib(validator=is_callable()) + value_validator = attrib(validator=is_callable()) + mapping_validator = attrib(default=None, validator=optional(is_callable())) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.mapping_validator is not None: + self.mapping_validator(inst, attr, value) + + for key in value: + self.key_validator(inst, attr, key) + self.value_validator(inst, attr, value[key]) + + def __repr__(self): + return ( + "" + ).format(key=self.key_validator, value=self.value_validator) + + +def deep_mapping(key_validator, value_validator, mapping_validator=None): + """ + A validator that performs deep validation of a dictionary. + + :param key_validator: Validator to apply to dictionary keys + :param value_validator: Validator to apply to dictionary values + :param mapping_validator: Validator to apply to top-level mapping + attribute (optional) + + .. versionadded:: 19.1.0 + + :raises TypeError: if any sub-validators fail + """ + return _DeepMapping(key_validator, value_validator, mapping_validator) + + +@attrs(repr=False, frozen=True, slots=True) +class _NumberValidator: + bound = attrib() + compare_op = attrib() + compare_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.compare_func(value, self.bound): + raise ValueError( + "'{name}' must be {op} {bound}: {value}".format( + name=attr.name, + op=self.compare_op, + bound=self.bound, + value=value, + ) + ) + + def __repr__(self): + return "".format( + op=self.compare_op, bound=self.bound + ) + + +def lt(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number larger or equal to *val*. + + :param val: Exclusive upper bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<", operator.lt) + + +def le(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number greater than *val*. + + :param val: Inclusive upper bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<=", operator.le) + + +def ge(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number smaller than *val*. + + :param val: Inclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">=", operator.ge) + + +def gt(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number smaller or equal to *val*. + + :param val: Exclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">", operator.gt) + + +@attrs(repr=False, frozen=True, slots=True) +class _MaxLengthValidator: + max_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) > self.max_length: + raise ValueError( + "Length of '{name}' must be <= {max}: {len}".format( + name=attr.name, max=self.max_length, len=len(value) + ) + ) + + def __repr__(self): + return f"" + + +def max_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is longer than *length*. + + :param int length: Maximum length of the string or iterable + + .. versionadded:: 21.3.0 + """ + return _MaxLengthValidator(length) + + +@attrs(repr=False, frozen=True, slots=True) +class _MinLengthValidator: + min_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) < self.min_length: + raise ValueError( + "Length of '{name}' must be => {min}: {len}".format( + name=attr.name, min=self.min_length, len=len(value) + ) + ) + + def __repr__(self): + return f"" + + +def min_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is shorter than *length*. + + :param int length: Minimum length of the string or iterable + + .. versionadded:: 22.1.0 + """ + return _MinLengthValidator(length) + + +@attrs(repr=False, slots=True, hash=True) +class _SubclassOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not issubclass(value, self.type): + raise TypeError( + "'{name}' must be a subclass of {type!r} " + "(got {value!r}).".format( + name=attr.name, + type=self.type, + value=value, + ), + attr, + self.type, + value, + ) + + def __repr__(self): + return "".format( + type=self.type + ) + + +def _subclass_of(type): + """ + A validator that raises a `TypeError` if the initializer is called + with a wrong type for this particular attribute (checks are performed using + `issubclass` therefore it's also valid to pass a tuple of types). + + :param type: The type to check for. + :type type: type or tuple of types + + :raises TypeError: With a human readable error message, the attribute + (of type `attrs.Attribute`), the expected type, and the value it + got. + """ + return _SubclassOfValidator(type) + + +@attrs(repr=False, slots=True, hash=True) +class _NotValidator: + validator = attrib() + msg = attrib( + converter=default_if_none( + "not_ validator child '{validator!r}' " + "did not raise a captured error" + ) + ) + exc_types = attrib( + validator=deep_iterable( + member_validator=_subclass_of(Exception), + iterable_validator=instance_of(tuple), + ), + ) + + def __call__(self, inst, attr, value): + try: + self.validator(inst, attr, value) + except self.exc_types: + pass # suppress error to invert validity + else: + raise ValueError( + self.msg.format( + validator=self.validator, + exc_types=self.exc_types, + ), + attr, + self.validator, + value, + self.exc_types, + ) + + def __repr__(self): + return ( + "" + ).format( + what=self.validator, + exc_types=self.exc_types, + ) + + +def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): + """ + A validator that wraps and logically 'inverts' the validator passed to it. + It will raise a `ValueError` if the provided validator *doesn't* raise a + `ValueError` or `TypeError` (by default), and will suppress the exception + if the provided validator *does*. + + Intended to be used with existing validators to compose logic without + needing to create inverted variants, for example, ``not_(in_(...))``. + + :param validator: A validator to be logically inverted. + :param msg: Message to raise if validator fails. + Formatted with keys ``exc_types`` and ``validator``. + :type msg: str + :param exc_types: Exception type(s) to capture. + Other types raised by child validators will not be intercepted and + pass through. + + :raises ValueError: With a human readable error message, + the attribute (of type `attrs.Attribute`), + the validator that failed to raise an exception, + the value it got, + and the expected exception types. + + .. versionadded:: 22.2.0 + """ + try: + exc_types = tuple(exc_types) + except TypeError: + exc_types = (exc_types,) + return _NotValidator(validator, msg, exc_types) diff --git a/lib/attr/validators.pyi b/lib/attr/validators.pyi new file mode 100644 index 0000000..fd9206d --- /dev/null +++ b/lib/attr/validators.pyi @@ -0,0 +1,86 @@ +from typing import ( + Any, + AnyStr, + Callable, + Container, + ContextManager, + Iterable, + List, + Mapping, + Match, + Optional, + Pattern, + Tuple, + Type, + TypeVar, + Union, + overload, +) + +from . import _ValidatorType +from . import _ValidatorArgType + +_T = TypeVar("_T") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_I = TypeVar("_I", bound=Iterable) +_K = TypeVar("_K") +_V = TypeVar("_V") +_M = TypeVar("_M", bound=Mapping) + +def set_disabled(run: bool) -> None: ... +def get_disabled() -> bool: ... +def disabled() -> ContextManager[None]: ... + +# To be more precise on instance_of use some overloads. +# If there are more than 3 items in the tuple then we fall back to Any +@overload +def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ... +@overload +def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ... +@overload +def instance_of( + type: Tuple[Type[_T1], Type[_T2]] +) -> _ValidatorType[Union[_T1, _T2]]: ... +@overload +def instance_of( + type: Tuple[Type[_T1], Type[_T2], Type[_T3]] +) -> _ValidatorType[Union[_T1, _T2, _T3]]: ... +@overload +def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... +def provides(interface: Any) -> _ValidatorType[Any]: ... +def optional( + validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] +) -> _ValidatorType[Optional[_T]]: ... +def in_(options: Container[_T]) -> _ValidatorType[_T]: ... +def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... +def matches_re( + regex: Union[Pattern[AnyStr], AnyStr], + flags: int = ..., + func: Optional[ + Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]] + ] = ..., +) -> _ValidatorType[AnyStr]: ... +def deep_iterable( + member_validator: _ValidatorArgType[_T], + iterable_validator: Optional[_ValidatorType[_I]] = ..., +) -> _ValidatorType[_I]: ... +def deep_mapping( + key_validator: _ValidatorType[_K], + value_validator: _ValidatorType[_V], + mapping_validator: Optional[_ValidatorType[_M]] = ..., +) -> _ValidatorType[_M]: ... +def is_callable() -> _ValidatorType[_T]: ... +def lt(val: _T) -> _ValidatorType[_T]: ... +def le(val: _T) -> _ValidatorType[_T]: ... +def ge(val: _T) -> _ValidatorType[_T]: ... +def gt(val: _T) -> _ValidatorType[_T]: ... +def max_len(length: int) -> _ValidatorType[_T]: ... +def min_len(length: int) -> _ValidatorType[_T]: ... +def not_( + validator: _ValidatorType[_T], + *, + msg: Optional[str] = None, + exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ... +) -> _ValidatorType[_T]: ... diff --git a/lib/attrs-22.2.0.dist-info/INSTALLER b/lib/attrs-22.2.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/attrs-22.2.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/attrs-22.2.0.dist-info/LICENSE b/lib/attrs-22.2.0.dist-info/LICENSE new file mode 100644 index 0000000..2bd6453 --- /dev/null +++ b/lib/attrs-22.2.0.dist-info/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack and the attrs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/attrs-22.2.0.dist-info/METADATA b/lib/attrs-22.2.0.dist-info/METADATA new file mode 100644 index 0000000..0f71b57 --- /dev/null +++ b/lib/attrs-22.2.0.dist-info/METADATA @@ -0,0 +1,278 @@ +Metadata-Version: 2.1 +Name: attrs +Version: 22.2.0 +Summary: Classes Without Boilerplate +Home-page: https://www.attrs.org/ +Author: Hynek Schlawack +Author-email: hs@ox.cx +Maintainer: Hynek Schlawack +Maintainer-email: hs@ox.cx +License: MIT +Project-URL: Documentation, https://www.attrs.org/ +Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html +Project-URL: Bug Tracker, https://github.com/python-attrs/attrs/issues +Project-URL: Source Code, https://github.com/python-attrs/attrs +Project-URL: Funding, https://github.com/sponsors/hynek +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi +Project-URL: Ko-fi, https://ko-fi.com/the_hynek +Keywords: class,attribute,boilerplate,dataclass +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.6 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: cov +Requires-Dist: attrs[tests] ; extra == 'cov' +Requires-Dist: coverage-enable-subprocess ; extra == 'cov' +Requires-Dist: coverage[toml] (>=5.3) ; extra == 'cov' +Provides-Extra: dev +Requires-Dist: attrs[docs,tests] ; extra == 'dev' +Provides-Extra: docs +Requires-Dist: furo ; extra == 'docs' +Requires-Dist: sphinx ; extra == 'docs' +Requires-Dist: myst-parser ; extra == 'docs' +Requires-Dist: zope.interface ; extra == 'docs' +Requires-Dist: sphinx-notfound-page ; extra == 'docs' +Requires-Dist: sphinxcontrib-towncrier ; extra == 'docs' +Requires-Dist: towncrier ; extra == 'docs' +Provides-Extra: tests +Requires-Dist: attrs[tests-no-zope] ; extra == 'tests' +Requires-Dist: zope.interface ; extra == 'tests' +Provides-Extra: tests-no-zope +Requires-Dist: hypothesis ; extra == 'tests-no-zope' +Requires-Dist: pympler ; extra == 'tests-no-zope' +Requires-Dist: pytest (>=4.3.0) ; extra == 'tests-no-zope' +Requires-Dist: pytest-xdist[psutil] ; extra == 'tests-no-zope' +Requires-Dist: cloudpickle ; (platform_python_implementation == "CPython") and extra == 'tests-no-zope' +Requires-Dist: mypy (<0.990,>=0.971) ; (platform_python_implementation == "CPython") and extra == 'tests-no-zope' +Requires-Dist: pytest-mypy-plugins ; (platform_python_implementation == "CPython" and python_version < "3.11") and extra == 'tests-no-zope' +Provides-Extra: tests_no_zope +Requires-Dist: hypothesis ; extra == 'tests_no_zope' +Requires-Dist: pympler ; extra == 'tests_no_zope' +Requires-Dist: pytest (>=4.3.0) ; extra == 'tests_no_zope' +Requires-Dist: pytest-xdist[psutil] ; extra == 'tests_no_zope' +Requires-Dist: cloudpickle ; (platform_python_implementation == "CPython") and extra == 'tests_no_zope' +Requires-Dist: mypy (<0.990,>=0.971) ; (platform_python_implementation == "CPython") and extra == 'tests_no_zope' +Requires-Dist: pytest-mypy-plugins ; (platform_python_implementation == "CPython" and python_version < "3.11") and extra == 'tests_no_zope' + +

+ + attrs + +

+ + +

+ + Documentation + + + License: MIT + + + + + + + Downloads per month + + DOI +

+ + + +*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)). +[Trusted by NASA](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/personalizing-your-profile#list-of-qualifying-repositories-for-mars-2020-helicopter-contributor-achievement) for Mars missions since 2020! + +Its main goal is to help you to write **concise** and **correct** software without slowing down your code. + + +## Sponsors + +*attrs* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek). +Especially those generously supporting us at the *The Organization* tier and higher: + +

+ + + + + + + + + + + + + + + +

+ +

+ Please consider joining them to help make attrs’s maintenance more sustainable! +

+ + + +## Example + +*attrs* gives you a class decorator and a way to declaratively define the attributes on that class: + + + +```pycon +>>> from attrs import asdict, define, make_class, Factory + +>>> @define +... class SomeClass: +... a_number: int = 42 +... list_of_numbers: list[int] = Factory(list) +... +... def hard_math(self, another_number): +... return self.a_number + sum(self.list_of_numbers) * another_number + + +>>> sc = SomeClass(1, [1, 2, 3]) +>>> sc +SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) + +>>> sc.hard_math(3) +19 +>>> sc == SomeClass(1, [1, 2, 3]) +True +>>> sc != SomeClass(2, [3, 2, 1]) +True + +>>> asdict(sc) +{'a_number': 1, 'list_of_numbers': [1, 2, 3]} + +>>> SomeClass() +SomeClass(a_number=42, list_of_numbers=[]) + +>>> C = make_class("C", ["a", "b"]) +>>> C("foo", "bar") +C(a='foo', b='bar') +``` + +After *declaring* your attributes, *attrs* gives you: + +- a concise and explicit overview of the class's attributes, +- a nice human-readable `__repr__`, +- equality-checking methods, +- an initializer, +- and much more, + +*without* writing dull boilerplate code again and again and *without* runtime performance penalties. + +**Hate type annotations**!? +No problem! +Types are entirely **optional** with *attrs*. +Simply assign `attrs.field()` to the attributes instead of annotating them with types. + +--- + +This example uses *attrs*'s modern APIs that have been introduced in version 20.1.0, and the *attrs* package import name that has been added in version 21.3.0. +The classic APIs (`@attr.s`, `attr.ib`, plus their serious-business aliases) and the `attr` package import name will remain **indefinitely**. + +Please check out [*On The Core API Names*](https://www.attrs.org/en/latest/names.html) for a more in-depth explanation. + + +## Data Classes + +On the tin, *attrs* might remind you of `dataclasses` (and indeed, `dataclasses` [are a descendant](https://hynek.me/articles/import-attrs/) of *attrs*). +In practice it does a lot more and is more flexible. +For instance it allows you to define [special handling of NumPy arrays for equality checks](https://www.attrs.org/en/stable/comparison.html#customization), or allows more ways to [plug into the initialization process](https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization). + +For more details, please refer to our [comparison page](https://www.attrs.org/en/stable/why.html#data-classes). + + +## Project Information + +- [**Changelog**](https://www.attrs.org/en/stable/changelog.html) +- [**Documentation**](https://www.attrs.org/) +- [**PyPI**](https://pypi.org/project/attrs/) +- [**Source Code**](https://github.com/python-attrs/attrs) +- [**Contributing**](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) +- [**Third-party Extensions**](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs) +- **License**: [MIT](https://www.attrs.org/en/latest/license.html) +- **Get Help**: please use the `python-attrs` tag on [StackOverflow](https://stackoverflow.com/questions/tagged/python-attrs) +- **Supported Python Versions**: 3.6 and later + + +### *attrs* for Enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of *attrs* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. +[Learn more.](https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + + +## Changes in This Release + +### Backwards-incompatible Changes + +- Python 3.5 is not supported anymore. + [#988](https://github.com/python-attrs/attrs/issues/988) + + +### Deprecations + +- Python 3.6 is now deprecated and support will be removed in the next release. + [#1017](https://github.com/python-attrs/attrs/issues/1017) + + +### Changes + +- `attrs.field()` now supports an *alias* option for explicit `__init__` argument names. + + Get `__init__` signatures matching any taste, peculiar or plain! + The [PEP 681 compatible](https://peps.python.org/pep-0681/#field-specifier-parameters) *alias* option can be use to override private attribute name mangling, or add other arbitrary field argument name overrides. + [#950](https://github.com/python-attrs/attrs/issues/950) +- `attrs.NOTHING` is now an enum value, making it possible to use with e.g. [`typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal). + [#983](https://github.com/python-attrs/attrs/issues/983) +- Added missing re-import of `attr.AttrsInstance` to the `attrs` namespace. + [#987](https://github.com/python-attrs/attrs/issues/987) +- Fix slight performance regression in classes with custom `__setattr__` and speedup even more. + [#991](https://github.com/python-attrs/attrs/issues/991) +- Class-creation performance improvements by switching performance-sensitive templating operations to f-strings. + + You can expect an improvement of about 5% -- even for very simple classes. + [#995](https://github.com/python-attrs/attrs/issues/995) +- `attrs.has()` is now a [`TypeGuard`](https://docs.python.org/3/library/typing.html#typing.TypeGuard) for `AttrsInstance`. + That means that type checkers know a class is an instance of an `attrs` class if you check it using `attrs.has()` (or `attr.has()`) first. + [#997](https://github.com/python-attrs/attrs/issues/997) +- Made `attrs.AttrsInstance` stub available at runtime and fixed type errors related to the usage of `attrs.AttrsInstance` in *Pyright*. + [#999](https://github.com/python-attrs/attrs/issues/999) +- On Python 3.10 and later, call [`abc.update_abstractmethods()`](https://docs.python.org/3/library/abc.html#abc.update_abstractmethods) on dict classes after creation. + This improves the detection of abstractness. + [#1001](https://github.com/python-attrs/attrs/issues/1001) +- *attrs*'s pickling methods now use dicts instead of tuples. + That is safer and more robust across different versions of a class. + [#1009](https://github.com/python-attrs/attrs/issues/1009) +- Added `attrs.validators.not_(wrapped_validator)` to logically invert *wrapped_validator* by accepting only values where *wrapped_validator* rejects the value with a `ValueError` or `TypeError` (by default, exception types configurable). + [#1010](https://github.com/python-attrs/attrs/issues/1010) +- The type stubs for `attrs.cmp_using()` now have default values. + [#1027](https://github.com/python-attrs/attrs/issues/1027) +- To conform with [PEP 681](https://peps.python.org/pep-0681/), `attr.s()` and `attrs.define()` now accept *unsafe_hash* in addition to *hash*. + [#1065](https://github.com/python-attrs/attrs/issues/1065) + +--- + +[Full changelog](https://www.attrs.org/en/stable/changelog.html) diff --git a/lib/attrs-22.2.0.dist-info/RECORD b/lib/attrs-22.2.0.dist-info/RECORD new file mode 100644 index 0000000..0527666 --- /dev/null +++ b/lib/attrs-22.2.0.dist-info/RECORD @@ -0,0 +1,56 @@ +attr/__init__.py,sha256=-lJ5CXKE5yKk97Z2HSMRJFiGz1TdXLU9q4Ysb2Id4IQ,1947 +attr/__init__.pyi,sha256=qOjUNync7Lq8NLk30l_DRTh1h62mMl1e4VnqBgY2x24,15831 +attr/__pycache__/__init__.cpython-39.pyc,, +attr/__pycache__/_cmp.cpython-39.pyc,, +attr/__pycache__/_compat.cpython-39.pyc,, +attr/__pycache__/_config.cpython-39.pyc,, +attr/__pycache__/_funcs.cpython-39.pyc,, +attr/__pycache__/_make.cpython-39.pyc,, +attr/__pycache__/_next_gen.cpython-39.pyc,, +attr/__pycache__/_version_info.cpython-39.pyc,, +attr/__pycache__/converters.cpython-39.pyc,, +attr/__pycache__/exceptions.cpython-39.pyc,, +attr/__pycache__/filters.cpython-39.pyc,, +attr/__pycache__/setters.cpython-39.pyc,, +attr/__pycache__/validators.cpython-39.pyc,, +attr/_cmp.py,sha256=mwr1ImJlkFL9Zi0E55-90IfchMKr94ko6e-p4y__M_4,4094 +attr/_cmp.pyi,sha256=sGQmOM0w3_K4-X8cTXR7g0Hqr290E8PTObA9JQxWQqc,399 +attr/_compat.py,sha256=Da-SeMicy7SkTKCCwKtfX41sUMf0o54tK96zsu1qE60,5435 +attr/_config.py,sha256=5W8lgRePuIOWu1ZuqF1899e2CmXGc95-ipwTpF1cEU4,826 +attr/_funcs.py,sha256=0EqqZgKNZBk4PXQvCF_fuWWAz14mSdZpk4UBZpX_fDQ,14545 +attr/_make.py,sha256=MdYHoWXJ2WlQNZPMTX4gkBO06QgPyb3qwSWSxaJ6QVg,96087 +attr/_next_gen.py,sha256=95DRKAfIuHbcwO9W_yWtRsHt3IbfxbAgpyB6agxbghw,6059 +attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469 +attr/_version_info.py,sha256=exSqb3b5E-fMSsgZAlEw9XcLpEgobPORCZpcaEglAM4,2121 +attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209 +attr/converters.py,sha256=xfGVSPRgWGcym6N5FZM9fyfvCQePqFyApWeC5BXKvoM,3602 +attr/converters.pyi,sha256=jKlpHBEt6HVKJvgrMFJRrHq8p61GXg4-Nd5RZWKJX7M,406 +attr/exceptions.py,sha256=ZGEMLv0CDY1TOtj49OF84myejOn-LCCXAKGIKalKkVU,1915 +attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539 +attr/filters.py,sha256=aZep54h8-4ZYV5lmZ3Dx2mqeQH4cMx6tuCmCylLNbEU,1038 +attr/filters.pyi,sha256=_Sm80jGySETX_Clzdkon5NHVjQWRl3Y3liQKZX1czXc,215 +attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attr/setters.py,sha256=pbCZQ-pE6ZxjDqZfWWUhUFefXtpekIU4qS_YDMLPQ50,1400 +attr/setters.pyi,sha256=pyY8TVNBu8TWhOldv_RxHzmGvdgFQH981db70r0fn5I,567 +attr/validators.py,sha256=gBJAzoo1UNDRTG9-kE0LUoUTgDr2slJymPxb6-UPt7c,20501 +attr/validators.pyi,sha256=ZbJDuF6Kom-L6ym9Cc6eT370S_a7z8YhWmP7z35ayXc,2538 +attrs-22.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +attrs-22.2.0.dist-info/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109 +attrs-22.2.0.dist-info/METADATA,sha256=jgQypZGK_yplaxCh1S1gnQ_NZYKk-EwtfWygdZ_NgIc,13531 +attrs-22.2.0.dist-info/RECORD,, +attrs-22.2.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 +attrs-22.2.0.dist-info/top_level.txt,sha256=AGbmKnOtYpdkLRsDRQVSBIwfL32pAQ6BSo1mt-BxI7M,11 +attrs/__init__.py,sha256=90bKLoqyIHpMjnzJuXSar1dH5anUQXHqT7-yI1Qzg00,1149 +attrs/__init__.pyi,sha256=KMHncABV_sq4pubLAli-iOQjc9EM3g9y2r6M9V71_vY,2148 +attrs/__pycache__/__init__.cpython-39.pyc,, +attrs/__pycache__/converters.cpython-39.pyc,, +attrs/__pycache__/exceptions.cpython-39.pyc,, +attrs/__pycache__/filters.cpython-39.pyc,, +attrs/__pycache__/setters.cpython-39.pyc,, +attrs/__pycache__/validators.cpython-39.pyc,, +attrs/converters.py,sha256=fCBEdlYWcmI3sCnpUk2pz22GYtXzqTkp6NeOpdI64PY,70 +attrs/exceptions.py,sha256=SlDli6AY77f6ny-H7oy98OkQjsrw-D_supEuErIVYkE,70 +attrs/filters.py,sha256=dc_dNey29kH6KLU1mT2Dakq7tZ3kBfzEGwzOmDzw1F8,67 +attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attrs/setters.py,sha256=oKw51C72Hh45wTwYvDHJP9kbicxiMhMR4Y5GvdpKdHQ,67 +attrs/validators.py,sha256=4ag1SyVD2Hm3PYKiNG_NOtR_e7f81Hr6GiNl4YvXo4Q,70 diff --git a/lib/attrs-22.2.0.dist-info/WHEEL b/lib/attrs-22.2.0.dist-info/WHEEL new file mode 100644 index 0000000..57e3d84 --- /dev/null +++ b/lib/attrs-22.2.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lib/attrs-22.2.0.dist-info/top_level.txt b/lib/attrs-22.2.0.dist-info/top_level.txt new file mode 100644 index 0000000..eca8ba9 --- /dev/null +++ b/lib/attrs-22.2.0.dist-info/top_level.txt @@ -0,0 +1,2 @@ +attr +attrs diff --git a/lib/attrs/__init__.py b/lib/attrs/__init__.py new file mode 100644 index 0000000..81dd6b2 --- /dev/null +++ b/lib/attrs/__init__.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MIT + +from attr import ( + NOTHING, + Attribute, + AttrsInstance, + Factory, + __author__, + __copyright__, + __description__, + __doc__, + __email__, + __license__, + __title__, + __url__, + __version__, + __version_info__, + assoc, + cmp_using, + define, + evolve, + field, + fields, + fields_dict, + frozen, + has, + make_class, + mutable, + resolve_types, + validate, +) +from attr._next_gen import asdict, astuple + +from . import converters, exceptions, filters, setters, validators + + +__all__ = [ + "__author__", + "__copyright__", + "__description__", + "__doc__", + "__email__", + "__license__", + "__title__", + "__url__", + "__version__", + "__version_info__", + "asdict", + "assoc", + "astuple", + "Attribute", + "AttrsInstance", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "Factory", + "field", + "fields_dict", + "fields", + "filters", + "frozen", + "has", + "make_class", + "mutable", + "NOTHING", + "resolve_types", + "setters", + "validate", + "validators", +] diff --git a/lib/attrs/__init__.pyi b/lib/attrs/__init__.pyi new file mode 100644 index 0000000..4ea64d8 --- /dev/null +++ b/lib/attrs/__init__.pyi @@ -0,0 +1,67 @@ +from typing import ( + Any, + Callable, + Dict, + Mapping, + Optional, + Sequence, + Tuple, + Type, +) + +# Because we need to type our own stuff, we have to make everything from +# attr explicitly public too. +from attr import __author__ as __author__ +from attr import __copyright__ as __copyright__ +from attr import __description__ as __description__ +from attr import __email__ as __email__ +from attr import __license__ as __license__ +from attr import __title__ as __title__ +from attr import __url__ as __url__ +from attr import __version__ as __version__ +from attr import __version_info__ as __version_info__ +from attr import _FilterType +from attr import assoc as assoc +from attr import Attribute as Attribute +from attr import AttrsInstance as AttrsInstance +from attr import cmp_using as cmp_using +from attr import converters as converters +from attr import define as define +from attr import evolve as evolve +from attr import exceptions as exceptions +from attr import Factory as Factory +from attr import field as field +from attr import fields as fields +from attr import fields_dict as fields_dict +from attr import filters as filters +from attr import frozen as frozen +from attr import has as has +from attr import make_class as make_class +from attr import mutable as mutable +from attr import NOTHING as NOTHING +from attr import resolve_types as resolve_types +from attr import setters as setters +from attr import validate as validate +from attr import validators as validators + +# TODO: see definition of attr.asdict/astuple +def asdict( + inst: Any, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + dict_factory: Type[Mapping[Any, Any]] = ..., + retain_collection_types: bool = ..., + value_serializer: Optional[ + Callable[[type, Attribute[Any], Any], Any] + ] = ..., + tuple_keys: bool = ..., +) -> Dict[str, Any]: ... + +# TODO: add support for returning NamedTuple from the mypy plugin +def astuple( + inst: Any, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + tuple_factory: Type[Sequence[Any]] = ..., + retain_collection_types: bool = ..., +) -> Tuple[Any, ...]: ... diff --git a/lib/attrs/__pycache__/__init__.cpython-39.pyc b/lib/attrs/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1400e7fead3b626fe973761dd30386645b5aa0af GIT binary patch literal 1092 zcmb7?OK;Oa5XYUT^JpG@H%(L8v>n4v`lX zVh;;Yz#&3KLKjzI71v-5 z*I^wuU;{T{6SrUsw_zK1U_>a#=}o`k~s zZ+K`@wXhAw_{>aG#h7g|7N*%k$;s3>v%^>$H`2s(bg?sa8!#72FEex*`(wytoVvIQ*E$VtJ;aR7v`i+p8i}z?gsqBs8qbBT zjHQSpTg^9Zo6DU=GBnPq($6BXr7738f&5CaFm@TOk2AwBV^_Bln$j?{$b`1FIge$; zjTjYemFql-&$XFlvA8Kv`zp2(rioK&oNC(;*P+PkcR9y0&Yiv%X8HCuTdMSEsH*OB zg`k0*|$lDQ-aeF^Gc(44TX@fg%i=jJFuI z{4^P(_!CP?iu986^U6|-N>Yo8S27ea1LeWQFJEV?n9yRNXiRW&QD#AmOKNd;Nq#|0 zdO=BiaYkZFYEeu{YH^8Cj8A4#OmSvOs%}AIa&}^RYHWHa^HWN5 MQtd!Cdk literal 0 HcmV?d00001 diff --git a/lib/attrs/__pycache__/exceptions.cpython-39.pyc b/lib/attrs/__pycache__/exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e961b8832ee9345bda9544f77c7773505f62c03f GIT binary patch literal 200 zcmYe~<>g`k0*|$lDQ-aeF^Gc(44TX@fg%i=jJFuI z{4^P(_!CP?iu6(|l2Z#xGV}9_S27ea1LeWQFJEV?n9$b_lGNf7qZpseq?qE&l2qM-#N_P6^wi=Qkg>)32y^ucDsOSvg`k0*|$lDb7IpF^Gc(44TX@fg%i=jJFuI z{4^P(coIuWiuBSlb4pT+idQleF#{#R#4j&rtC-MYplD2Ra#3bMj7w^9c1eCgOnN~{ zd~rr%N@`I|NosM4QH)P!QcQ7XNvdu^Vsdt3dTMbD$joAWsG)iVmA5!-a`RJ4b5iX< J#(xGm003!5GS&b9 literal 0 HcmV?d00001 diff --git a/lib/attrs/__pycache__/setters.cpython-39.pyc b/lib/attrs/__pycache__/setters.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..532428b08070cda59eb5f842f2aaef13217a1e35 GIT binary patch literal 194 zcmYe~<>g`k0*|$lDb7IpF^Gc(44TX@fg%i=jJFuI z{4^P(coIuWiu8(8OG;9UidQleF#{#R#4j&rtC-MYplD2Ra#3bMj7w^9c1eCgOnN~{ zd~rr%N@`I|NosM4QH)P!QcQ7XNvdu^Vsdt3dTMbD$joAWsG)iVmA5!-a`RJ4b5iX< J#(xGm0045WGWY-h literal 0 HcmV?d00001 diff --git a/lib/attrs/__pycache__/validators.cpython-39.pyc b/lib/attrs/__pycache__/validators.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f980b197b058bb01d95582ae6212619972be44dc GIT binary patch literal 200 zcmYe~<>g`k0*|$lDQ-aeF^Gc(44TX@fg%i=jJFuI z{4^P(_!CP?iuB47b23vBOY)0~S27ea1LeWQFJEV?n9$b_lGNf7qZpseq?qE&l2qM-#N_P6^wi=Qkg>)32y^ucDsOSv=3.6 +License-File: LICENSE + +Certifi: Python SSL Certificates +================================ + +Certifi provides Mozilla's carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python3.7/site-packages/certifi/cacert.pem + +Enjoy! + +1024-bit Root Certificates +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Browsers and certificate authorities have concluded that 1024-bit keys are +unacceptably weak for certificates, particularly root certificates. For this +reason, Mozilla has removed any weak (i.e. 1024-bit key) certificate from its +bundle, replacing it with an equivalent strong (i.e. 2048-bit or greater key) +certificate from the same CA. Because Mozilla removed these certificates from +its bundle, ``certifi`` removed them as well. + +In previous versions, ``certifi`` provided the ``certifi.old_where()`` function +to intentionally re-add the 1024-bit roots back into your bundle. This was not +recommended in production and therefore was removed at the end of 2018. + +.. _`Requests`: https://requests.readthedocs.io/en/master/ + +Addition/Removal of Certificates +-------------------------------- + +Certifi does not support any addition/removal or other modification of the +CA trust store content. This project is intended to provide a reliable and +highly portable root of trust to python deployments. Look to upstream projects +for methods to use alternate trust. + + diff --git a/lib/certifi-2022.12.7.dist-info/RECORD b/lib/certifi-2022.12.7.dist-info/RECORD new file mode 100644 index 0000000..9ea663a --- /dev/null +++ b/lib/certifi-2022.12.7.dist-info/RECORD @@ -0,0 +1,14 @@ +certifi-2022.12.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +certifi-2022.12.7.dist-info/LICENSE,sha256=oC9sY4-fuE0G93ZMOrCF2K9-2luTwWbaVDEkeQd8b7A,1052 +certifi-2022.12.7.dist-info/METADATA,sha256=chFpcxKhCPEQ3d8-Vz36zr2Micf1eQhKkFFk7_JvJNo,2911 +certifi-2022.12.7.dist-info/RECORD,, +certifi-2022.12.7.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +certifi-2022.12.7.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi/__init__.py,sha256=bK_nm9bLJzNvWZc2oZdiTwg2KWD4HSPBWGaM0zUDvMw,94 +certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 +certifi/__pycache__/__init__.cpython-39.pyc,, +certifi/__pycache__/__main__.cpython-39.pyc,, +certifi/__pycache__/core.cpython-39.pyc,, +certifi/cacert.pem,sha256=LBHDzgj_xA05AxnHK8ENT5COnGNElNZe0svFUHMf1SQ,275233 +certifi/core.py,sha256=lhewz0zFb2b4ULsQurElmloYwQoecjWzPqY67P8T7iM,4219 +certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/lib/certifi-2022.12.7.dist-info/WHEEL b/lib/certifi-2022.12.7.dist-info/WHEEL new file mode 100644 index 0000000..5bad85f --- /dev/null +++ b/lib/certifi-2022.12.7.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lib/certifi-2022.12.7.dist-info/top_level.txt b/lib/certifi-2022.12.7.dist-info/top_level.txt new file mode 100644 index 0000000..963eac5 --- /dev/null +++ b/lib/certifi-2022.12.7.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/lib/certifi/__init__.py b/lib/certifi/__init__.py new file mode 100644 index 0000000..a3546f1 --- /dev/null +++ b/lib/certifi/__init__.py @@ -0,0 +1,4 @@ +from .core import contents, where + +__all__ = ["contents", "where"] +__version__ = "2022.12.07" diff --git a/lib/certifi/__main__.py b/lib/certifi/__main__.py new file mode 100644 index 0000000..8945b5d --- /dev/null +++ b/lib/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/lib/certifi/__pycache__/__init__.cpython-39.pyc b/lib/certifi/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2cefff9cc135ebc0e0e466b2eb55aee3e9cdd6d GIT binary patch literal 281 zcmYk0KT8BL5XF=1uD6^V*xK1{;d-VKL`2ZSLTwJ4B{;-n+zYpVLJ~dBZ)D*YN^9j; zurk}-gZT}QH#|lz7ITtx|9z}q(*BvuzF0ChY5YloCYpAnrw#2{!zj^AXDw?ooj18I zR-`)krRl6?s^y(w*VHG@zh@uA(6`=^S1eUlN+ikmln52kad-fgb(St9P zt0&LiJejN_4&&uarU;YK3#tI&Ho5pbL5V`b)tx(OOpA_mb79StAZ?` z1jwfMLb4FTd87i>Q=y8Uy&rD+&ga1A49{T;*qW>8f(rYrlau>Ne31q*A%N*v)L*xxUx7*J;>dS({gaBaia(b$PNcZX834Nr7c6PC+kB z(~4YPH@Bjb+ literal 0 HcmV?d00001 diff --git a/lib/certifi/__pycache__/core.cpython-39.pyc b/lib/certifi/__pycache__/core.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d2a7bccfb0fa260372e97e3f2f85c55ade66258 GIT binary patch literal 1888 zcmbVMOK;>v5bkcsEz3P_6-qG*#9U`DdM?w-W+ut(J$ zAWN1@_Q;Aqu!qbsNc^F`a>`!_9H?&3WD>Gmu+`O7-R^SLS6_9~Yz73M2Y;;dpWB4| zX^g8+6^+kv$O{ylM1)h$hBTsr_8E5u9&F)?J9I{FYv-HO^L-mfD{9$!AzHw>HEfR-qeV)@ zqFCrJ3BSkINwkcX&ljw|!f%UZu_Ao)@}(CZU*;>%yr?;iZuQ!~Zk7MyY?p*sNjb~(E*_jA(tI>AUattRWckVDl78Nut-a#=ylS>c-k0? zQKx`Trer5AawT#lH-dA7nQVvb+`;+0jv{2bn#j194TbiMlp)oX@<*xp`s(ANv57;z zi$Y<+4hg)YsA8O+&{K!A3C@YDJmsENCIof-YL}cj+qybU?;@DT7glv6-n_s0<-@&r z=lN#h==$u7&Ao56BNgac9Ah>D;`offf;nQu#aEm6x{ss)+1*Vc z8+X4Da-fQFcYmy6d5~}cT_vRYsJoRt?#fJwyW=DsB>O^kr?I`C79bFR?frNlKrEtH z>7yXNUtgjP24>|3C5}PGGt(^bJJ5;Ro9>gCZ_+6_$}O> zmhq0FhU+IRVaieL)SZ_PtVa(_D{FsWsMxx0pb1SQT1H$JVjio->to!Uq1Xrz#T((6 zf?EKM9|IuA)<@u+T>?wEbVh9&Fa3i_d<(}E2;{E!D94U?QZMAj4kECX4a9|asCsul z(G_G*ma5Rx)oJOq4?-4)fCN}YFS;TC3Xo7iBQC;oJ?}R=rb@v$Io|qd5 zgEHhqY*-;-9aUZB8RW@GXg{|1LmX?@v?YQ}d;bo{v_=m{8P5MHGrWq&ueh~@vZax~ad#= (3, 11): + + from importlib.resources import as_file, files + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + + return _CACERT_PATH + + def contents() -> str: + return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +elif sys.version_info >= (3, 7): + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + + return _CACERT_PATH + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") + +else: + import os + import types + from typing import Union + + Package = Union[types.ModuleType, str] + Resource = Union[str, "os.PathLike"] + + # This fallback will work for Python versions prior to 3.7 that lack the + # importlib.resources module but relies on the existing `where` function + # so won't address issues with environments like PyOxidizer that don't set + # __file__ on modules. + def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict' + ) -> str: + with open(where(), encoding=encoding) as data: + return data.read() + + # If we don't have importlib.resources, then we will just do the old logic + # of assuming we're on the filesystem and munge the path directly. + def where() -> str: + f = os.path.dirname(__file__) + + return os.path.join(f, "cacert.pem") + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/lib/certifi/py.typed b/lib/certifi/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lib/charset_normalizer-3.1.0.dist-info/INSTALLER b/lib/charset_normalizer-3.1.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/charset_normalizer-3.1.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/charset_normalizer-3.1.0.dist-info/LICENSE b/lib/charset_normalizer-3.1.0.dist-info/LICENSE new file mode 100644 index 0000000..ad82355 --- /dev/null +++ b/lib/charset_normalizer-3.1.0.dist-info/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/lib/charset_normalizer-3.1.0.dist-info/METADATA b/lib/charset_normalizer-3.1.0.dist-info/METADATA new file mode 100644 index 0000000..867b915 --- /dev/null +++ b/lib/charset_normalizer-3.1.0.dist-info/METADATA @@ -0,0 +1,616 @@ +Metadata-Version: 2.1 +Name: charset-normalizer +Version: 3.1.0 +Summary: The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet. +Home-page: https://github.com/Ousret/charset_normalizer +Author: Ahmed TAHRI +Author-email: ahmed.tahri@cloudnursery.dev +License: MIT +Project-URL: Bug Reports, https://github.com/Ousret/charset_normalizer/issues +Project-URL: Documentation, https://charset-normalizer.readthedocs.io/en/latest +Keywords: encoding,charset,charset-detector,detector,normalization,unicode,chardet,detect +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Text Processing :: Linguistic +Classifier: Topic :: Utilities +Classifier: Typing :: Typed +Requires-Python: >=3.7.0 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: unicode_backport + +

Charset Detection, for Everyone 👋

+ +

+ The Real First Universal Charset Detector
+ + + + + + + + Download Count Total + +

+ +> A library that helps you read text from an unknown charset encoding.
Motivated by `chardet`, +> I'm trying to resolve the issue by taking a new approach. +> All IANA character set names for which the Python core library provides codecs are supported. + +

+ >>>>> 👉 Try Me Online Now, Then Adopt Me 👈 <<<<< +

+ +This project offers you an alternative to **Universal Charset Encoding Detector**, also known as **Chardet**. + +| Feature | [Chardet](https://github.com/chardet/chardet) | Charset Normalizer | [cChardet](https://github.com/PyYoshi/cChardet) | +|--------------------------------------------------|:---------------------------------------------:|:------------------------------------------------------------------------------------------------------:|:-----------------------------------------------:| +| `Fast` | ❌
| ✅
| ✅
| +| `Universal**` | ❌ | ✅ | ❌ | +| `Reliable` **without** distinguishable standards | ❌ | ✅ | ✅ | +| `Reliable` **with** distinguishable standards | ✅ | ✅ | ✅ | +| `License` | LGPL-2.1
_restrictive_ | MIT | MPL-1.1
_restrictive_ | +| `Native Python` | ✅ | ✅ | ❌ | +| `Detect spoken language` | ❌ | ✅ | N/A | +| `UnicodeDecodeError Safety` | ❌ | ✅ | ❌ | +| `Whl Size` | 193.6 kB | 39.5 kB | ~200 kB | +| `Supported Encoding` | 33 | :tada: [90](https://charset-normalizer.readthedocs.io/en/latest/user/support.html#supported-encodings) | 40 | + +

+Reading Normalized TextCat Reading Text + +*\*\* : They are clearly using specific code for a specific encoding even if covering most of used one*
+Did you got there because of the logs? See [https://charset-normalizer.readthedocs.io/en/latest/user/miscellaneous.html](https://charset-normalizer.readthedocs.io/en/latest/user/miscellaneous.html) + +## ⭐ Your support + +*Fork, test-it, star-it, submit your ideas! We do listen.* + +## ⚡ Performance + +This package offer better performance than its counterpart Chardet. Here are some numbers. + +| Package | Accuracy | Mean per file (ms) | File per sec (est) | +|-----------------------------------------------|:--------:|:------------------:|:------------------:| +| [chardet](https://github.com/chardet/chardet) | 86 % | 200 ms | 5 file/sec | +| charset-normalizer | **98 %** | **10 ms** | 100 file/sec | + +| Package | 99th percentile | 95th percentile | 50th percentile | +|-----------------------------------------------|:---------------:|:---------------:|:---------------:| +| [chardet](https://github.com/chardet/chardet) | 1200 ms | 287 ms | 23 ms | +| charset-normalizer | 100 ms | 50 ms | 5 ms | + +Chardet's performance on larger file (1MB+) are very poor. Expect huge difference on large payload. + +> Stats are generated using 400+ files using default parameters. More details on used files, see GHA workflows. +> And yes, these results might change at any time. The dataset can be updated to include more files. +> The actual delays heavily depends on your CPU capabilities. The factors should remain the same. +> Keep in mind that the stats are generous and that Chardet accuracy vs our is measured using Chardet initial capability +> (eg. Supported Encoding) Challenge-them if you want. + +## ✨ Installation + +Using PyPi for latest stable +```sh +pip install charset-normalizer -U +``` + +## 🚀 Basic Usage + +### CLI +This package comes with a CLI. + +``` +usage: normalizer [-h] [-v] [-a] [-n] [-m] [-r] [-f] [-t THRESHOLD] + file [file ...] + +The Real First Universal Charset Detector. Discover originating encoding used +on text file. Normalize text to unicode. + +positional arguments: + files File(s) to be analysed + +optional arguments: + -h, --help show this help message and exit + -v, --verbose Display complementary information about file if any. + Stdout will contain logs about the detection process. + -a, --with-alternative + Output complementary possibilities if any. Top-level + JSON WILL be a list. + -n, --normalize Permit to normalize input file. If not set, program + does not write anything. + -m, --minimal Only output the charset detected to STDOUT. Disabling + JSON output. + -r, --replace Replace file when trying to normalize it instead of + creating a new one. + -f, --force Replace file without asking if you are sure, use this + flag with caution. + -t THRESHOLD, --threshold THRESHOLD + Define a custom maximum amount of chaos allowed in + decoded content. 0. <= chaos <= 1. + --version Show version information and exit. +``` + +```bash +normalizer ./data/sample.1.fr.srt +``` + +:tada: Since version 1.4.0 the CLI produce easily usable stdout result in JSON format. + +```json +{ + "path": "/home/default/projects/charset_normalizer/data/sample.1.fr.srt", + "encoding": "cp1252", + "encoding_aliases": [ + "1252", + "windows_1252" + ], + "alternative_encodings": [ + "cp1254", + "cp1256", + "cp1258", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + "mbcs" + ], + "language": "French", + "alphabets": [ + "Basic Latin", + "Latin-1 Supplement" + ], + "has_sig_or_bom": false, + "chaos": 0.149, + "coherence": 97.152, + "unicode_path": null, + "is_preferred": true +} +``` + +### Python +*Just print out normalized text* +```python +from charset_normalizer import from_path + +results = from_path('./my_subtitle.srt') + +print(str(results.best())) +``` + +*Upgrade your code without effort* +```python +from charset_normalizer import detect +``` + +The above code will behave the same as **chardet**. We ensure that we offer the best (reasonable) BC result possible. + +See the docs for advanced usage : [readthedocs.io](https://charset-normalizer.readthedocs.io/en/latest/) + +## 😇 Why + +When I started using Chardet, I noticed that it was not suited to my expectations, and I wanted to propose a +reliable alternative using a completely different method. Also! I never back down on a good challenge! + +I **don't care** about the **originating charset** encoding, because **two different tables** can +produce **two identical rendered string.** +What I want is to get readable text, the best I can. + +In a way, **I'm brute forcing text decoding.** How cool is that ? 😎 + +Don't confuse package **ftfy** with charset-normalizer or chardet. ftfy goal is to repair unicode string whereas charset-normalizer to convert raw file in unknown encoding to unicode. + +## 🍰 How + + - Discard all charset encoding table that could not fit the binary content. + - Measure noise, or the mess once opened (by chunks) with a corresponding charset encoding. + - Extract matches with the lowest mess detected. + - Additionally, we measure coherence / probe for a language. + +**Wait a minute**, what is noise/mess and coherence according to **YOU ?** + +*Noise :* I opened hundred of text files, **written by humans**, with the wrong encoding table. **I observed**, then +**I established** some ground rules about **what is obvious** when **it seems like** a mess. + I know that my interpretation of what is noise is probably incomplete, feel free to contribute in order to + improve or rewrite it. + +*Coherence :* For each language there is on earth, we have computed ranked letter appearance occurrences (the best we can). So I thought +that intel is worth something here. So I use those records against decoded text to check if I can detect intelligent design. + +## ⚡ Known limitations + + - Language detection is unreliable when text contains two or more languages sharing identical letters. (eg. HTML (english tags) + Turkish content (Sharing Latin characters)) + - Every charset detector heavily depends on sufficient content. In common cases, do not bother run detection on very tiny content. + +## ⚠️ About Python EOLs + +**If you are running:** + +- Python >=2.7,<3.5: Unsupported +- Python 3.5: charset-normalizer < 2.1 +- Python 3.6: charset-normalizer < 3.1 + +Upgrade your Python interpreter as soon as possible. + +## 👤 Contributing + +Contributions, issues and feature requests are very much welcome.
+Feel free to check [issues page](https://github.com/ousret/charset_normalizer/issues) if you want to contribute. + +## 📝 License + +Copyright © [Ahmed TAHRI @Ousret](https://github.com/Ousret).
+This project is [MIT](https://github.com/Ousret/charset_normalizer/blob/master/LICENSE) licensed. + +Characters frequencies used in this project © 2012 [Denny Vrandečić](http://simia.net/letters/) + +## 💼 For Enterprise + +Professional support for charset-normalizer is available as part of the [Tidelift +Subscription][1]. Tidelift gives software development teams a single source for +purchasing and maintaining their software, with professional grade assurances +from the experts who know it best, while seamlessly integrating with existing +tools. + +[1]: https://tidelift.com/subscription/pkg/pypi-charset-normalizer?utm_source=pypi-charset-normalizer&utm_medium=readme + +# Changelog +All notable changes to charset-normalizer will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [3.1.0](https://github.com/Ousret/charset_normalizer/compare/3.0.1...3.1.0) (2023-03-06) + +### Added +- Argument `should_rename_legacy` for legacy function `detect` and disregard any new arguments without errors (PR #262) + +### Removed +- Support for Python 3.6 (PR #260) + +### Changed +- Optional speedup provided by mypy/c 1.0.1 + +## [3.0.1](https://github.com/Ousret/charset_normalizer/compare/3.0.0...3.0.1) (2022-11-18) + +### Fixed +- Multi-bytes cutter/chunk generator did not always cut correctly (PR #233) + +### Changed +- Speedup provided by mypy/c 0.990 on Python >= 3.7 + +## [3.0.0](https://github.com/Ousret/charset_normalizer/compare/2.1.1...3.0.0) (2022-10-20) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it +- Sphinx warnings when generating the documentation + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [3.0.0rc1](https://github.com/Ousret/charset_normalizer/compare/3.0.0b2...3.0.0rc1) (2022-10-18) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' + +## [3.0.0b2](https://github.com/Ousret/charset_normalizer/compare/3.0.0b1...3.0.0b2) (2022-08-21) + +### Added +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Removed +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) + +### Fixed +- Sphinx warnings when generating the documentation + +## [3.0.0b1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...3.0.0b1) (2022-08-15) + +### Changed +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Removed +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [2.1.1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...2.1.1) (2022-08-19) + +### Deprecated +- Function `normalize` scheduled for removal in 3.0 + +### Changed +- Removed useless call to decode in fn is_unprintable (#206) + +### Fixed +- Third-party library (i18n xgettext) crashing not recognizing utf_8 (PEP 263) with underscore from [@aleksandernovikov](https://github.com/aleksandernovikov) (#204) + +## [2.1.0](https://github.com/Ousret/charset_normalizer/compare/2.0.12...2.1.0) (2022-06-19) + +### Added +- Output the Unicode table version when running the CLI with `--version` (PR #194) + +### Changed +- Re-use decoded buffer for single byte character sets from [@nijel](https://github.com/nijel) (PR #175) +- Fixing some performance bottlenecks from [@deedy5](https://github.com/deedy5) (PR #183) + +### Fixed +- Workaround potential bug in cpython with Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space (PR #175) +- CLI default threshold aligned with the API threshold from [@oleksandr-kuzmenko](https://github.com/oleksandr-kuzmenko) (PR #181) + +### Removed +- Support for Python 3.5 (PR #192) + +### Deprecated +- Use of backport unicodedata from `unicodedata2` as Python is quickly catching up, scheduled for removal in 3.0 (PR #194) + +## [2.0.12](https://github.com/Ousret/charset_normalizer/compare/2.0.11...2.0.12) (2022-02-12) + +### Fixed +- ASCII miss-detection on rare cases (PR #170) + +## [2.0.11](https://github.com/Ousret/charset_normalizer/compare/2.0.10...2.0.11) (2022-01-30) + +### Added +- Explicit support for Python 3.11 (PR #164) + +### Changed +- The logging behavior have been completely reviewed, now using only TRACE and DEBUG levels (PR #163 #165) + +## [2.0.10](https://github.com/Ousret/charset_normalizer/compare/2.0.9...2.0.10) (2022-01-04) + +### Fixed +- Fallback match entries might lead to UnicodeDecodeError for large bytes sequence (PR #154) + +### Changed +- Skipping the language-detection (CD) on ASCII (PR #155) + +## [2.0.9](https://github.com/Ousret/charset_normalizer/compare/2.0.8...2.0.9) (2021-12-03) + +### Changed +- Moderating the logging impact (since 2.0.8) for specific environments (PR #147) + +### Fixed +- Wrong logging level applied when setting kwarg `explain` to True (PR #146) + +## [2.0.8](https://github.com/Ousret/charset_normalizer/compare/2.0.7...2.0.8) (2021-11-24) +### Changed +- Improvement over Vietnamese detection (PR #126) +- MD improvement on trailing data and long foreign (non-pure latin) data (PR #124) +- Efficiency improvements in cd/alphabet_languages from [@adbar](https://github.com/adbar) (PR #122) +- call sum() without an intermediary list following PEP 289 recommendations from [@adbar](https://github.com/adbar) (PR #129) +- Code style as refactored by Sourcery-AI (PR #131) +- Minor adjustment on the MD around european words (PR #133) +- Remove and replace SRTs from assets / tests (PR #139) +- Initialize the library logger with a `NullHandler` by default from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Setting kwarg `explain` to True will add provisionally (bounded to function lifespan) a specific stream handler (PR #135) + +### Fixed +- Fix large (misleading) sequence giving UnicodeDecodeError (PR #137) +- Avoid using too insignificant chunk (PR #137) + +### Added +- Add and expose function `set_logging_handler` to configure a specific StreamHandler from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Add `CHANGELOG.md` entries, format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) (PR #141) + +## [2.0.7](https://github.com/Ousret/charset_normalizer/compare/2.0.6...2.0.7) (2021-10-11) +### Added +- Add support for Kazakh (Cyrillic) language detection (PR #109) + +### Changed +- Further, improve inferring the language from a given single-byte code page (PR #112) +- Vainly trying to leverage PEP263 when PEP3120 is not supported (PR #116) +- Refactoring for potential performance improvements in loops from [@adbar](https://github.com/adbar) (PR #113) +- Various detection improvement (MD+CD) (PR #117) + +### Removed +- Remove redundant logging entry about detected language(s) (PR #115) + +### Fixed +- Fix a minor inconsistency between Python 3.5 and other versions regarding language detection (PR #117 #102) + +## [2.0.6](https://github.com/Ousret/charset_normalizer/compare/2.0.5...2.0.6) (2021-09-18) +### Fixed +- Unforeseen regression with the loss of the backward-compatibility with some older minor of Python 3.5.x (PR #100) +- Fix CLI crash when using --minimal output in certain cases (PR #103) + +### Changed +- Minor improvement to the detection efficiency (less than 1%) (PR #106 #101) + +## [2.0.5](https://github.com/Ousret/charset_normalizer/compare/2.0.4...2.0.5) (2021-09-14) +### Changed +- The project now comply with: flake8, mypy, isort and black to ensure a better overall quality (PR #81) +- The BC-support with v1.x was improved, the old staticmethods are restored (PR #82) +- The Unicode detection is slightly improved (PR #93) +- Add syntax sugar \_\_bool\_\_ for results CharsetMatches list-container (PR #91) + +### Removed +- The project no longer raise warning on tiny content given for detection, will be simply logged as warning instead (PR #92) + +### Fixed +- In some rare case, the chunks extractor could cut in the middle of a multi-byte character and could mislead the mess detection (PR #95) +- Some rare 'space' characters could trip up the UnprintablePlugin/Mess detection (PR #96) +- The MANIFEST.in was not exhaustive (PR #78) + +## [2.0.4](https://github.com/Ousret/charset_normalizer/compare/2.0.3...2.0.4) (2021-07-30) +### Fixed +- The CLI no longer raise an unexpected exception when no encoding has been found (PR #70) +- Fix accessing the 'alphabets' property when the payload contains surrogate characters (PR #68) +- The logger could mislead (explain=True) on detected languages and the impact of one MBCS match (PR #72) +- Submatch factoring could be wrong in rare edge cases (PR #72) +- Multiple files given to the CLI were ignored when publishing results to STDOUT. (After the first path) (PR #72) +- Fix line endings from CRLF to LF for certain project files (PR #67) + +### Changed +- Adjust the MD to lower the sensitivity, thus improving the global detection reliability (PR #69 #76) +- Allow fallback on specified encoding if any (PR #71) + +## [2.0.3](https://github.com/Ousret/charset_normalizer/compare/2.0.2...2.0.3) (2021-07-16) +### Changed +- Part of the detection mechanism has been improved to be less sensitive, resulting in more accurate detection results. Especially ASCII. (PR #63) +- According to the community wishes, the detection will fall back on ASCII or UTF-8 in a last-resort case. (PR #64) + +## [2.0.2](https://github.com/Ousret/charset_normalizer/compare/2.0.1...2.0.2) (2021-07-15) +### Fixed +- Empty/Too small JSON payload miss-detection fixed. Report from [@tseaver](https://github.com/tseaver) (PR #59) + +### Changed +- Don't inject unicodedata2 into sys.modules from [@akx](https://github.com/akx) (PR #57) + +## [2.0.1](https://github.com/Ousret/charset_normalizer/compare/2.0.0...2.0.1) (2021-07-13) +### Fixed +- Make it work where there isn't a filesystem available, dropping assets frequencies.json. Report from [@sethmlarson](https://github.com/sethmlarson). (PR #55) +- Using explain=False permanently disable the verbose output in the current runtime (PR #47) +- One log entry (language target preemptive) was not show in logs when using explain=True (PR #47) +- Fix undesired exception (ValueError) on getitem of instance CharsetMatches (PR #52) + +### Changed +- Public function normalize default args values were not aligned with from_bytes (PR #53) + +### Added +- You may now use charset aliases in cp_isolation and cp_exclusion arguments (PR #47) + +## [2.0.0](https://github.com/Ousret/charset_normalizer/compare/1.4.1...2.0.0) (2021-07-02) +### Changed +- 4x to 5 times faster than the previous 1.4.0 release. At least 2x faster than Chardet. +- Accent has been made on UTF-8 detection, should perform rather instantaneous. +- The backward compatibility with Chardet has been greatly improved. The legacy detect function returns an identical charset name whenever possible. +- The detection mechanism has been slightly improved, now Turkish content is detected correctly (most of the time) +- The program has been rewritten to ease the readability and maintainability. (+Using static typing)+ +- utf_7 detection has been reinstated. + +### Removed +- This package no longer require anything when used with Python 3.5 (Dropped cached_property) +- Removed support for these languages: Catalan, Esperanto, Kazakh, Baque, Volapük, Azeri, Galician, Nynorsk, Macedonian, and Serbocroatian. +- The exception hook on UnicodeDecodeError has been removed. + +### Deprecated +- Methods coherence_non_latin, w_counter, chaos_secondary_pass of the class CharsetMatch are now deprecated and scheduled for removal in v3.0 + +### Fixed +- The CLI output used the relative path of the file(s). Should be absolute. + +## [1.4.1](https://github.com/Ousret/charset_normalizer/compare/1.4.0...1.4.1) (2021-05-28) +### Fixed +- Logger configuration/usage no longer conflict with others (PR #44) + +## [1.4.0](https://github.com/Ousret/charset_normalizer/compare/1.3.9...1.4.0) (2021-05-21) +### Removed +- Using standard logging instead of using the package loguru. +- Dropping nose test framework in favor of the maintained pytest. +- Choose to not use dragonmapper package to help with gibberish Chinese/CJK text. +- Require cached_property only for Python 3.5 due to constraint. Dropping for every other interpreter version. +- Stop support for UTF-7 that does not contain a SIG. +- Dropping PrettyTable, replaced with pure JSON output in CLI. + +### Fixed +- BOM marker in a CharsetNormalizerMatch instance could be False in rare cases even if obviously present. Due to the sub-match factoring process. +- Not searching properly for the BOM when trying utf32/16 parent codec. + +### Changed +- Improving the package final size by compressing frequencies.json. +- Huge improvement over the larges payload. + +### Added +- CLI now produces JSON consumable output. +- Return ASCII if given sequences fit. Given reasonable confidence. + +## [1.3.9](https://github.com/Ousret/charset_normalizer/compare/1.3.8...1.3.9) (2021-05-13) + +### Fixed +- In some very rare cases, you may end up getting encode/decode errors due to a bad bytes payload (PR #40) + +## [1.3.8](https://github.com/Ousret/charset_normalizer/compare/1.3.7...1.3.8) (2021-05-12) + +### Fixed +- Empty given payload for detection may cause an exception if trying to access the `alphabets` property. (PR #39) + +## [1.3.7](https://github.com/Ousret/charset_normalizer/compare/1.3.6...1.3.7) (2021-05-12) + +### Fixed +- The legacy detect function should return UTF-8-SIG if sig is present in the payload. (PR #38) + +## [1.3.6](https://github.com/Ousret/charset_normalizer/compare/1.3.5...1.3.6) (2021-02-09) + +### Changed +- Amend the previous release to allow prettytable 2.0 (PR #35) + +## [1.3.5](https://github.com/Ousret/charset_normalizer/compare/1.3.4...1.3.5) (2021-02-08) + +### Fixed +- Fix error while using the package with a python pre-release interpreter (PR #33) + +### Changed +- Dependencies refactoring, constraints revised. + +### Added +- Add python 3.9 and 3.10 to the supported interpreters + +MIT License + +Copyright (c) 2019 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/charset_normalizer-3.1.0.dist-info/RECORD b/lib/charset_normalizer-3.1.0.dist-info/RECORD new file mode 100644 index 0000000..30faa4b --- /dev/null +++ b/lib/charset_normalizer-3.1.0.dist-info/RECORD @@ -0,0 +1,35 @@ +../../Scripts/normalizer.exe,sha256=tA1DHrn6RVXHUJsWbQJYgVPViVKG8_srwocKZOR0ZxU,106392 +charset_normalizer-3.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +charset_normalizer-3.1.0.dist-info/LICENSE,sha256=znnj1Var_lZ-hzOvD5W50wcQDp9qls3SD2xIau88ufc,1090 +charset_normalizer-3.1.0.dist-info/METADATA,sha256=HZvzFq-GOwJuV67nudLDOsXsN5aaskaTpDRYJwF7ItY,31599 +charset_normalizer-3.1.0.dist-info/RECORD,, +charset_normalizer-3.1.0.dist-info/WHEEL,sha256=J_4V_gB-O6Y7Pn6lk91K27JaIhI-q07YM5J8Ufzqla4,100 +charset_normalizer-3.1.0.dist-info/entry_points.txt,sha256=uYo8aIGLWv8YgWfSna5HnfY_En4pkF1w4bgawNAXzP0,76 +charset_normalizer-3.1.0.dist-info/top_level.txt,sha256=7ASyzePr8_xuZWJsnqJjIBtyV8vhEo0wBCv1MPRRi3Q,19 +charset_normalizer/__init__.py,sha256=e1hmY5TS8uSqQqk4O2zg42Ua6pyff1OkIBHLsk_IHsg,1594 +charset_normalizer/__pycache__/__init__.cpython-39.pyc,, +charset_normalizer/__pycache__/api.cpython-39.pyc,, +charset_normalizer/__pycache__/cd.cpython-39.pyc,, +charset_normalizer/__pycache__/constant.cpython-39.pyc,, +charset_normalizer/__pycache__/legacy.cpython-39.pyc,, +charset_normalizer/__pycache__/md.cpython-39.pyc,, +charset_normalizer/__pycache__/models.cpython-39.pyc,, +charset_normalizer/__pycache__/utils.cpython-39.pyc,, +charset_normalizer/__pycache__/version.cpython-39.pyc,, +charset_normalizer/api.py,sha256=HsD2RTIObAYNIHF2F8bPDdl52-zui2kFPVFkC9CBgO4,19178 +charset_normalizer/assets/__init__.py,sha256=OyBZPVNqIZAj-0nouvNuQJfAJuhUqfZnR8qD0CQXW9s,21509 +charset_normalizer/assets/__pycache__/__init__.cpython-39.pyc,, +charset_normalizer/cd.py,sha256=hbC1uvKjlSWkAoZEhyAA0E-L3-sGMKR7d2hLoB3iKfw,12944 +charset_normalizer/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer/cli/__pycache__/__init__.cpython-39.pyc,, +charset_normalizer/cli/__pycache__/normalizer.cpython-39.pyc,, +charset_normalizer/cli/normalizer.py,sha256=rs-cBipBzr7d0TAaUa0nG4qrjXhdddeCVB-f6Xt_wS0,10040 +charset_normalizer/constant.py,sha256=Gonvxywzx8z-DotOjM3Jok0qx-QG9vSrUdjboWBNS3E,19596 +charset_normalizer/legacy.py,sha256=KbJxEpu7g6zE2uXSB3T-3178cgiSQdVJlJmY-gv3EAM,2125 +charset_normalizer/md.cp39-win_amd64.pyd,sha256=QTtg1QcqSQwS8Q2RREwA3Z1RuXZrdWI97C3X8aH_HVU,10752 +charset_normalizer/md.py,sha256=hIvXc7zpP8FRFUiZ5xeTr0-8A5S0lFIeIst4tP455JA,18829 +charset_normalizer/md__mypyc.cp39-win_amd64.pyd,sha256=c0St5wTEXAq1B3Zb7QHZktjG5m-Jfue18Zckci3-oFE,116736 +charset_normalizer/models.py,sha256=PlJCwiLQUg4KfUe2orz5NCwlhYmfeemHG2_ETR9xZ4U,11829 +charset_normalizer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer/utils.py,sha256=Dt9jpK6Fla3-snlK8o3t3XgklEPKOzGzP9P803aqPwQ,11958 +charset_normalizer/version.py,sha256=UuTHwG12Vfey6sXu2INf_WDhXWkGj0H5C3moExM38YQ,85 diff --git a/lib/charset_normalizer-3.1.0.dist-info/WHEEL b/lib/charset_normalizer-3.1.0.dist-info/WHEEL new file mode 100644 index 0000000..d22c9ab --- /dev/null +++ b/lib/charset_normalizer-3.1.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: false +Tag: cp39-cp39-win_amd64 + diff --git a/lib/charset_normalizer-3.1.0.dist-info/entry_points.txt b/lib/charset_normalizer-3.1.0.dist-info/entry_points.txt new file mode 100644 index 0000000..a06d360 --- /dev/null +++ b/lib/charset_normalizer-3.1.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +normalizer = charset_normalizer.cli.normalizer:cli_detect diff --git a/lib/charset_normalizer-3.1.0.dist-info/top_level.txt b/lib/charset_normalizer-3.1.0.dist-info/top_level.txt new file mode 100644 index 0000000..66958f0 --- /dev/null +++ b/lib/charset_normalizer-3.1.0.dist-info/top_level.txt @@ -0,0 +1 @@ +charset_normalizer diff --git a/lib/charset_normalizer/__init__.py b/lib/charset_normalizer/__init__.py new file mode 100644 index 0000000..ebb5da8 --- /dev/null +++ b/lib/charset_normalizer/__init__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +Charset-Normalizer +~~~~~~~~~~~~~~ +The Real First Universal Charset Detector. +A library that helps you read text from an unknown charset encoding. +Motivated by chardet, This package is trying to resolve the issue by taking a new approach. +All IANA character set names for which the Python core library provides codecs are supported. + +Basic usage: + >>> from charset_normalizer import from_bytes + >>> results = from_bytes('Bсеки човек има право на образование. Oбразованието!'.encode('utf_8')) + >>> best_guess = results.best() + >>> str(best_guess) + 'Bсеки човек има право на образование. Oбразованието!' + +Others methods and usages are available - see the full documentation +at . +:copyright: (c) 2021 by Ahmed TAHRI +:license: MIT, see LICENSE for more details. +""" +import logging + +from .api import from_bytes, from_fp, from_path +from .legacy import detect +from .models import CharsetMatch, CharsetMatches +from .utils import set_logging_handler +from .version import VERSION, __version__ + +__all__ = ( + "from_fp", + "from_path", + "from_bytes", + "detect", + "CharsetMatch", + "CharsetMatches", + "__version__", + "VERSION", + "set_logging_handler", +) + +# Attach a NullHandler to the top level logger by default +# https://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library + +logging.getLogger("charset_normalizer").addHandler(logging.NullHandler()) diff --git a/lib/charset_normalizer/__pycache__/__init__.cpython-39.pyc b/lib/charset_normalizer/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..caaf5e53506a04bad73ac624557e31a5793470f4 GIT binary patch literal 1552 zcmcIkOK&4Z5T3CeJDxn*jfA)rr^M36hMbVKSY)$7iIg}f$s#neG-}UOJgxLR)ZOD? z4hZ(b-`E^EAO)lq5rXg+CfB)g+P}bw>ajN)6t0XkrLOAgs;{1GY00tZ+xzK*pj)@B zzYKH#s}b`V{Yn!FEY}L`pdxJ74yr*-RHCX|jcRT!nseuDi)3@69yQ!Xd9I83sOdH% z$8}1+Ar_)Vcd))gwARzO*iFl2=Zw)bJXbTM7Eu65PT?^oPZ9Q zh7d)nV3K4Ykp-afYYk^IiGanB#piKy5raSV4;cGNz~fQdIZQMkGmQZZC*^g3`U#wd zTtUkGb2dVtqm~oW2Az;?m54EtOLL(zH19M!H`feeya1M_GGTs50EK}4?f!OYm-!kc zQ1B452o;wel0bxNO+yUZo|=kiEm%#Hy^II%lzZUdZy33 zPu5$lw`hi_v^UC789*~=Z6n;cQ&C!Oyi+U1|2JI6Iihk)1rbt}0;)tDO!Gc13L7&n z*iayBQh}$vah6f>gT&7wj5X6di5=?Er=ixV>UKIKuET8D_LHb{lqre2^RQ^Ww$t^K zbRzjE)Lq!{Td?)f*2kvdw!?@z_H=vic;D#??qjU53y1rsPf7p>`#Zb+lijk%BGXdT z)C5troj0@qto|FDXscQ{Ww$xZi^~Q`PAN>`Jygx#QaI=p{PeRNvV<8Z&@X&jy3>!Gs_m%@OJYiVkJrupro*23Rcgsm?RzM@znMARF@cS3BLo3q=7B zPJGGJ!E;pSI!OnkRC_990m=bQq23xC@ZmsljhnM|qXrLy?RXxKx%RwvIw|I#QV^NI XUWNRqRU);u`O2ETXs?>TQd{{4TA(T_ literal 0 HcmV?d00001 diff --git a/lib/charset_normalizer/__pycache__/api.cpython-39.pyc b/lib/charset_normalizer/__pycache__/api.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8970f768d3a50c3a813a0f25c9aa8a244cccf36 GIT binary patch literal 10378 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 literal 0 HcmV?d00001 diff --git a/lib/charset_normalizer/__pycache__/legacy.cpython-39.pyc b/lib/charset_normalizer/__pycache__/legacy.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b810ae8ff42516b0e821e25da9a2ea5a45c3d963 GIT binary patch literal 1829 zcmZ`)Pj4JG6d%ucW@k5>G%Y1+q#{`Y+N{)u3L$|aM5&UE%D)sPDMG82R-U_%TWjAe#g&)PLSgcMo#Z%VIFlNSO*kmwLI>`)jG`T zd85;S^&FdHO@^4y=2^fn$c2Hw>@9mv(%LgjV_N?q>|CLZ9k11V0r9*Rw!s52ws?c5 z#>S5d!=*?v8$K4WXd#Oa6D3|Cw-wpOK*_w<9~-9O*V6jK2iqI%-QN16?d|r?*N?U~ z+FR@GP4F4JJw0udy{<>H%xF(Bk>sqGv0;*qZL|+=huTJpnNo?=ce%_)4QBU!0iXU8 zgz+Z+2~xKVqKNvZz6mD&DL(T~&>8v(;P^Q>LC7=V#50kp4g3>dZU&%w3`64SJl z$dm%oXC#-}WMk4#Qbz#2o65XM4DV-bwQ}9Q@aM=Phdj$jo|tq*)@RwsLqQG|H!kMk zhzWMU6e*`ZiJgw6>N9JH5rs5bv;L5Onz(I= zps%G}YET(Taz0dGCSzGbR3-Nu4lL0;8{Z+RgfQ6@7b?>-XTm^j;Cdic6>v~008J4t zKm&DwZI!r;i!2cWLV%hmmdk@ATWwTF)`~}is7 zF~f0o?7w;+TF`CbwUrJ4bKb252rC?vfa2LV$Utb@V~|X#p$1 zN%kw`UDJ(=DHfUlb`F$8sRY;NrMeEH69QmT+n_IVRR=|@rrZmy=A3AP zu<%M;ZdY%D=2uU>4b#hS*4Mg^HB-8~lPX?xH<;cxvgi&A)6=5_80tbk=Kb!Myx-N_ zu)77^Lnu{uXDszzvGm@wCsqr1ET;^(boD(D-a?FgbmJW31(-34&i!*7Hc;$0{cGqZ zTEa`PUPN)=gWdmIy$ffyT4=MiXd|c`?yhnVt_qyMPJ|$A?4Eis>M71aQt%3+PVBB0 zz-uZ*)J?Y;_od8i;GXz!kjcbUQ7Rx_6Yo@hKZf=!Gxmx57txC#ZlI>$L{03S`zS(7 G|NINW5*tYX literal 0 HcmV?d00001 diff --git a/lib/charset_normalizer/__pycache__/md.cpython-39.pyc b/lib/charset_normalizer/__pycache__/md.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4275d8cc6c5d3f483dec27ca5bb44cadcb586554 GIT binary patch literal 15121 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 literal 0 HcmV?d00001 diff --git a/lib/charset_normalizer/__pycache__/models.cpython-39.pyc b/lib/charset_normalizer/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a14b23667f6ac66604535ed62215068b67f27dc GIT binary patch literal 11438 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 literal 0 HcmV?d00001 diff --git a/lib/charset_normalizer/__pycache__/version.cpython-39.pyc b/lib/charset_normalizer/__pycache__/version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e539a0bb352ff1c338995453eddb044cc173fe5e GIT binary patch literal 263 zcmYe~<>g`k0*|$lDWO36F^GcKWW()brD1xy2qIpOT*(AAgHGJ|3z9$YCum z$jL0Z#UADw6zu8mx00cV4QL0L_!aJK6%$$vG%O}KxhS(B#wE2lyClCLCcU5}zBnT> zCABD~B(=E2D8?r CharsetMatches: + """ + Given a raw bytes sequence, return the best possibles charset usable to render str objects. + If there is no results, it is a strong indicator that the source is binary/not text. + By default, the process will extract 5 blocks of 512o each to assess the mess and coherence of a given sequence. + And will give up a particular code page after 20% of measured mess. Those criteria are customizable at will. + + The preemptive behavior DOES NOT replace the traditional detection workflow, it prioritize a particular code page + but never take it for granted. Can improve the performance. + + You may want to focus your attention to some code page or/and not others, use cp_isolation and cp_exclusion for that + purpose. + + This function will strip the SIG in the payload/sequence every time except on UTF-16, UTF-32. + By default the library does not setup any handler other than the NullHandler, if you choose to set the 'explain' + toggle to True it will alter the logger configuration to add a StreamHandler that is suitable for debugging. + Custom logging format and handler can be set manually. + """ + + if not isinstance(sequences, (bytearray, bytes)): + raise TypeError( + "Expected object of type bytes or bytearray, got: {0}".format( + type(sequences) + ) + ) + + if explain: + previous_logger_level: int = logger.level + logger.addHandler(explain_handler) + logger.setLevel(TRACE) + + length: int = len(sequences) + + if length == 0: + logger.debug("Encoding detection on empty bytes, assuming utf_8 intention.") + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level or logging.WARNING) + return CharsetMatches([CharsetMatch(sequences, "utf_8", 0.0, False, [], "")]) + + if cp_isolation is not None: + logger.log( + TRACE, + "cp_isolation is set. use this flag for debugging purpose. " + "limited list of encoding allowed : %s.", + ", ".join(cp_isolation), + ) + cp_isolation = [iana_name(cp, False) for cp in cp_isolation] + else: + cp_isolation = [] + + if cp_exclusion is not None: + logger.log( + TRACE, + "cp_exclusion is set. use this flag for debugging purpose. " + "limited list of encoding excluded : %s.", + ", ".join(cp_exclusion), + ) + cp_exclusion = [iana_name(cp, False) for cp in cp_exclusion] + else: + cp_exclusion = [] + + if length <= (chunk_size * steps): + logger.log( + TRACE, + "override steps (%i) and chunk_size (%i) as content does not fit (%i byte(s) given) parameters.", + steps, + chunk_size, + length, + ) + steps = 1 + chunk_size = length + + if steps > 1 and length / steps < chunk_size: + chunk_size = int(length / steps) + + is_too_small_sequence: bool = len(sequences) < TOO_SMALL_SEQUENCE + is_too_large_sequence: bool = len(sequences) >= TOO_BIG_SEQUENCE + + if is_too_small_sequence: + logger.log( + TRACE, + "Trying to detect encoding from a tiny portion of ({}) byte(s).".format( + length + ), + ) + elif is_too_large_sequence: + logger.log( + TRACE, + "Using lazy str decoding because the payload is quite large, ({}) byte(s).".format( + length + ), + ) + + prioritized_encodings: List[str] = [] + + specified_encoding: Optional[str] = ( + any_specified_encoding(sequences) if preemptive_behaviour else None + ) + + if specified_encoding is not None: + prioritized_encodings.append(specified_encoding) + logger.log( + TRACE, + "Detected declarative mark in sequence. Priority +1 given for %s.", + specified_encoding, + ) + + tested: Set[str] = set() + tested_but_hard_failure: List[str] = [] + tested_but_soft_failure: List[str] = [] + + fallback_ascii: Optional[CharsetMatch] = None + fallback_u8: Optional[CharsetMatch] = None + fallback_specified: Optional[CharsetMatch] = None + + results: CharsetMatches = CharsetMatches() + + sig_encoding, sig_payload = identify_sig_or_bom(sequences) + + if sig_encoding is not None: + prioritized_encodings.append(sig_encoding) + logger.log( + TRACE, + "Detected a SIG or BOM mark on first %i byte(s). Priority +1 given for %s.", + len(sig_payload), + sig_encoding, + ) + + prioritized_encodings.append("ascii") + + if "utf_8" not in prioritized_encodings: + prioritized_encodings.append("utf_8") + + for encoding_iana in prioritized_encodings + IANA_SUPPORTED: + if cp_isolation and encoding_iana not in cp_isolation: + continue + + if cp_exclusion and encoding_iana in cp_exclusion: + continue + + if encoding_iana in tested: + continue + + tested.add(encoding_iana) + + decoded_payload: Optional[str] = None + bom_or_sig_available: bool = sig_encoding == encoding_iana + strip_sig_or_bom: bool = bom_or_sig_available and should_strip_sig_or_bom( + encoding_iana + ) + + if encoding_iana in {"utf_16", "utf_32"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because it require a BOM. Will try some sub-encoder LE/BE.", + encoding_iana, + ) + continue + if encoding_iana in {"utf_7"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because detection is unreliable without BOM/SIG.", + encoding_iana, + ) + continue + + try: + is_multi_byte_decoder: bool = is_multi_byte_encoding(encoding_iana) + except (ModuleNotFoundError, ImportError): + logger.log( + TRACE, + "Encoding %s does not provide an IncrementalDecoder", + encoding_iana, + ) + continue + + try: + if is_too_large_sequence and is_multi_byte_decoder is False: + str( + sequences[: int(50e4)] + if strip_sig_or_bom is False + else sequences[len(sig_payload) : int(50e4)], + encoding=encoding_iana, + ) + else: + decoded_payload = str( + sequences + if strip_sig_or_bom is False + else sequences[len(sig_payload) :], + encoding=encoding_iana, + ) + except (UnicodeDecodeError, LookupError) as e: + if not isinstance(e, LookupError): + logger.log( + TRACE, + "Code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + similar_soft_failure_test: bool = False + + for encoding_soft_failed in tested_but_soft_failure: + if is_cp_similar(encoding_iana, encoding_soft_failed): + similar_soft_failure_test = True + break + + if similar_soft_failure_test: + logger.log( + TRACE, + "%s is deemed too similar to code page %s and was consider unsuited already. Continuing!", + encoding_iana, + encoding_soft_failed, + ) + continue + + r_ = range( + 0 if not bom_or_sig_available else len(sig_payload), + length, + int(length / steps), + ) + + multi_byte_bonus: bool = ( + is_multi_byte_decoder + and decoded_payload is not None + and len(decoded_payload) < length + ) + + if multi_byte_bonus: + logger.log( + TRACE, + "Code page %s is a multi byte encoding table and it appear that at least one character " + "was encoded using n-bytes.", + encoding_iana, + ) + + max_chunk_gave_up: int = int(len(r_) / 4) + + max_chunk_gave_up = max(max_chunk_gave_up, 2) + early_stop_count: int = 0 + lazy_str_hard_failure = False + + md_chunks: List[str] = [] + md_ratios = [] + + try: + for chunk in cut_sequence_chunks( + sequences, + encoding_iana, + r_, + chunk_size, + bom_or_sig_available, + strip_sig_or_bom, + sig_payload, + is_multi_byte_decoder, + decoded_payload, + ): + md_chunks.append(chunk) + + md_ratios.append( + mess_ratio( + chunk, + threshold, + explain is True and 1 <= len(cp_isolation) <= 2, + ) + ) + + if md_ratios[-1] >= threshold: + early_stop_count += 1 + + if (early_stop_count >= max_chunk_gave_up) or ( + bom_or_sig_available and strip_sig_or_bom is False + ): + break + except ( + UnicodeDecodeError + ) as e: # Lazy str loading may have missed something there + logger.log( + TRACE, + "LazyStr Loading: After MD chunk decode, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + early_stop_count = max_chunk_gave_up + lazy_str_hard_failure = True + + # We might want to check the sequence again with the whole content + # Only if initial MD tests passes + if ( + not lazy_str_hard_failure + and is_too_large_sequence + and not is_multi_byte_decoder + ): + try: + sequences[int(50e3) :].decode(encoding_iana, errors="strict") + except UnicodeDecodeError as e: + logger.log( + TRACE, + "LazyStr Loading: After final lookup, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + mean_mess_ratio: float = sum(md_ratios) / len(md_ratios) if md_ratios else 0.0 + if mean_mess_ratio >= threshold or early_stop_count >= max_chunk_gave_up: + tested_but_soft_failure.append(encoding_iana) + logger.log( + TRACE, + "%s was excluded because of initial chaos probing. Gave up %i time(s). " + "Computed mean chaos is %f %%.", + encoding_iana, + early_stop_count, + round(mean_mess_ratio * 100, ndigits=3), + ) + # Preparing those fallbacks in case we got nothing. + if ( + encoding_iana in ["ascii", "utf_8", specified_encoding] + and not lazy_str_hard_failure + ): + fallback_entry = CharsetMatch( + sequences, encoding_iana, threshold, False, [], decoded_payload + ) + if encoding_iana == specified_encoding: + fallback_specified = fallback_entry + elif encoding_iana == "ascii": + fallback_ascii = fallback_entry + else: + fallback_u8 = fallback_entry + continue + + logger.log( + TRACE, + "%s passed initial chaos probing. Mean measured chaos is %f %%", + encoding_iana, + round(mean_mess_ratio * 100, ndigits=3), + ) + + if not is_multi_byte_decoder: + target_languages: List[str] = encoding_languages(encoding_iana) + else: + target_languages = mb_encoding_languages(encoding_iana) + + if target_languages: + logger.log( + TRACE, + "{} should target any language(s) of {}".format( + encoding_iana, str(target_languages) + ), + ) + + cd_ratios = [] + + # We shall skip the CD when its about ASCII + # Most of the time its not relevant to run "language-detection" on it. + if encoding_iana != "ascii": + for chunk in md_chunks: + chunk_languages = coherence_ratio( + chunk, + language_threshold, + ",".join(target_languages) if target_languages else None, + ) + + cd_ratios.append(chunk_languages) + + cd_ratios_merged = merge_coherence_ratios(cd_ratios) + + if cd_ratios_merged: + logger.log( + TRACE, + "We detected language {} using {}".format( + cd_ratios_merged, encoding_iana + ), + ) + + results.append( + CharsetMatch( + sequences, + encoding_iana, + mean_mess_ratio, + bom_or_sig_available, + cd_ratios_merged, + decoded_payload, + ) + ) + + if ( + encoding_iana in [specified_encoding, "ascii", "utf_8"] + and mean_mess_ratio < 0.1 + ): + logger.debug( + "Encoding detection: %s is most likely the one.", encoding_iana + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([results[encoding_iana]]) + + if encoding_iana == sig_encoding: + logger.debug( + "Encoding detection: %s is most likely the one as we detected a BOM or SIG within " + "the beginning of the sequence.", + encoding_iana, + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([results[encoding_iana]]) + + if len(results) == 0: + if fallback_u8 or fallback_ascii or fallback_specified: + logger.log( + TRACE, + "Nothing got out of the detection process. Using ASCII/UTF-8/Specified fallback.", + ) + + if fallback_specified: + logger.debug( + "Encoding detection: %s will be used as a fallback match", + fallback_specified.encoding, + ) + results.append(fallback_specified) + elif ( + (fallback_u8 and fallback_ascii is None) + or ( + fallback_u8 + and fallback_ascii + and fallback_u8.fingerprint != fallback_ascii.fingerprint + ) + or (fallback_u8 is not None) + ): + logger.debug("Encoding detection: utf_8 will be used as a fallback match") + results.append(fallback_u8) + elif fallback_ascii: + logger.debug("Encoding detection: ascii will be used as a fallback match") + results.append(fallback_ascii) + + if results: + logger.debug( + "Encoding detection: Found %s as plausible (best-candidate) for content. With %i alternatives.", + results.best().encoding, # type: ignore + len(results) - 1, + ) + else: + logger.debug("Encoding detection: Unable to determine any suitable charset.") + + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return results + + +def from_fp( + fp: BinaryIO, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: Optional[List[str]] = None, + cp_exclusion: Optional[List[str]] = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but using a file pointer that is already ready. + Will not close the file pointer. + """ + return from_bytes( + fp.read(), + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + ) + + +def from_path( + path: "PathLike[Any]", + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: Optional[List[str]] = None, + cp_exclusion: Optional[List[str]] = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but with one extra step. Opening and reading given file path in binary mode. + Can raise IOError. + """ + with open(path, "rb") as fp: + return from_fp( + fp, + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + ) diff --git a/lib/charset_normalizer/assets/__init__.py b/lib/charset_normalizer/assets/__init__.py new file mode 100644 index 0000000..9075930 --- /dev/null +++ b/lib/charset_normalizer/assets/__init__.py @@ -0,0 +1,1440 @@ +# -*- coding: utf-8 -*- +from typing import Dict, List + +# Language label that contain the em dash "—" +# character are to be considered alternative seq to origin +FREQUENCIES: Dict[str, List[str]] = { + "English": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "u", + "m", + "f", + "p", + "g", + "w", + "y", + "b", + "v", + "k", + "x", + "j", + "z", + "q", + ], + "English—": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "m", + "u", + "f", + "p", + "g", + "w", + "b", + "y", + "v", + "k", + "j", + "x", + "z", + "q", + ], + "German": [ + "e", + "n", + "i", + "r", + "s", + "t", + "a", + "d", + "h", + "u", + "l", + "g", + "o", + "c", + "m", + "b", + "f", + "k", + "w", + "z", + "p", + "v", + "ü", + "ä", + "ö", + "j", + ], + "French": [ + "e", + "a", + "s", + "n", + "i", + "t", + "r", + "l", + "u", + "o", + "d", + "c", + "p", + "m", + "é", + "v", + "g", + "f", + "b", + "h", + "q", + "à", + "x", + "è", + "y", + "j", + ], + "Dutch": [ + "e", + "n", + "a", + "i", + "r", + "t", + "o", + "d", + "s", + "l", + "g", + "h", + "v", + "m", + "u", + "k", + "c", + "p", + "b", + "w", + "j", + "z", + "f", + "y", + "x", + "ë", + ], + "Italian": [ + "e", + "i", + "a", + "o", + "n", + "l", + "t", + "r", + "s", + "c", + "d", + "u", + "p", + "m", + "g", + "v", + "f", + "b", + "z", + "h", + "q", + "è", + "à", + "k", + "y", + "ò", + ], + "Polish": [ + "a", + "i", + "o", + "e", + "n", + "r", + "z", + "w", + "s", + "c", + "t", + "k", + "y", + "d", + "p", + "m", + "u", + "l", + "j", + "ł", + "g", + "b", + "h", + "ą", + "ę", + "ó", + ], + "Spanish": [ + "e", + "a", + "o", + "n", + "s", + "r", + "i", + "l", + "d", + "t", + "c", + "u", + "m", + "p", + "b", + "g", + "v", + "f", + "y", + "ó", + "h", + "q", + "í", + "j", + "z", + "á", + ], + "Russian": [ + "о", + "а", + "е", + "и", + "н", + "с", + "т", + "р", + "в", + "л", + "к", + "м", + "д", + "п", + "у", + "г", + "я", + "ы", + "з", + "б", + "й", + "ь", + "ч", + "х", + "ж", + "ц", + ], + # Jap-Kanji + "Japanese": [ + "人", + "一", + "大", + "亅", + "丁", + "丨", + "竹", + "笑", + "口", + "日", + "今", + "二", + "彳", + "行", + "十", + "土", + "丶", + "寸", + "寺", + "時", + "乙", + "丿", + "乂", + "气", + "気", + "冂", + "巾", + "亠", + "市", + "目", + "儿", + "見", + "八", + "小", + "凵", + "県", + "月", + "彐", + "門", + "間", + "木", + "東", + "山", + "出", + "本", + "中", + "刀", + "分", + "耳", + "又", + "取", + "最", + "言", + "田", + "心", + "思", + "刂", + "前", + "京", + "尹", + "事", + "生", + "厶", + "云", + "会", + "未", + "来", + "白", + "冫", + "楽", + "灬", + "馬", + "尸", + "尺", + "駅", + "明", + "耂", + "者", + "了", + "阝", + "都", + "高", + "卜", + "占", + "厂", + "广", + "店", + "子", + "申", + "奄", + "亻", + "俺", + "上", + "方", + "冖", + "学", + "衣", + "艮", + "食", + "自", + ], + # Jap-Katakana + "Japanese—": [ + "ー", + "ン", + "ス", + "・", + "ル", + "ト", + "リ", + "イ", + "ア", + "ラ", + "ッ", + "ク", + "ド", + "シ", + "レ", + "ジ", + "タ", + "フ", + "ロ", + "カ", + "テ", + "マ", + "ィ", + "グ", + "バ", + "ム", + "プ", + "オ", + "コ", + "デ", + "ニ", + "ウ", + "メ", + "サ", + "ビ", + "ナ", + "ブ", + "ャ", + "エ", + "ュ", + "チ", + "キ", + "ズ", + "ダ", + "パ", + "ミ", + "ェ", + "ョ", + "ハ", + "セ", + "ベ", + "ガ", + "モ", + "ツ", + "ネ", + "ボ", + "ソ", + "ノ", + "ァ", + "ヴ", + "ワ", + "ポ", + "ペ", + "ピ", + "ケ", + "ゴ", + "ギ", + "ザ", + "ホ", + "ゲ", + "ォ", + "ヤ", + "ヒ", + "ユ", + "ヨ", + "ヘ", + "ゼ", + "ヌ", + "ゥ", + "ゾ", + "ヶ", + "ヂ", + "ヲ", + "ヅ", + "ヵ", + "ヱ", + "ヰ", + "ヮ", + "ヽ", + "゠", + "ヾ", + "ヷ", + "ヿ", + "ヸ", + "ヹ", + "ヺ", + ], + # Jap-Hiragana + "Japanese——": [ + "の", + "に", + "る", + "た", + "と", + "は", + "し", + "い", + "を", + "で", + "て", + "が", + "な", + "れ", + "か", + "ら", + "さ", + "っ", + "り", + "す", + "あ", + "も", + "こ", + "ま", + "う", + "く", + "よ", + "き", + "ん", + "め", + "お", + "け", + "そ", + "つ", + "だ", + "や", + "え", + "ど", + "わ", + "ち", + "み", + "せ", + "じ", + "ば", + "へ", + "び", + "ず", + "ろ", + "ほ", + "げ", + "む", + "べ", + "ひ", + "ょ", + "ゆ", + "ぶ", + "ご", + "ゃ", + "ね", + "ふ", + "ぐ", + "ぎ", + "ぼ", + "ゅ", + "づ", + "ざ", + "ぞ", + "ぬ", + "ぜ", + "ぱ", + "ぽ", + "ぷ", + "ぴ", + "ぃ", + "ぁ", + "ぇ", + "ぺ", + "ゞ", + "ぢ", + "ぉ", + "ぅ", + "ゐ", + "ゝ", + "ゑ", + "゛", + "゜", + "ゎ", + "ゔ", + "゚", + "ゟ", + "゙", + "ゕ", + "ゖ", + ], + "Portuguese": [ + "a", + "e", + "o", + "s", + "i", + "r", + "d", + "n", + "t", + "m", + "u", + "c", + "l", + "p", + "g", + "v", + "b", + "f", + "h", + "ã", + "q", + "é", + "ç", + "á", + "z", + "í", + ], + "Swedish": [ + "e", + "a", + "n", + "r", + "t", + "s", + "i", + "l", + "d", + "o", + "m", + "k", + "g", + "v", + "h", + "f", + "u", + "p", + "ä", + "c", + "b", + "ö", + "å", + "y", + "j", + "x", + ], + "Chinese": [ + "的", + "一", + "是", + "不", + "了", + "在", + "人", + "有", + "我", + "他", + "这", + "个", + "们", + "中", + "来", + "上", + "大", + "为", + "和", + "国", + "地", + "到", + "以", + "说", + "时", + "要", + "就", + "出", + "会", + "可", + "也", + "你", + "对", + "生", + "能", + "而", + "子", + "那", + "得", + "于", + "着", + "下", + "自", + "之", + "年", + "过", + "发", + "后", + "作", + "里", + "用", + "道", + "行", + "所", + "然", + "家", + "种", + "事", + "成", + "方", + "多", + "经", + "么", + "去", + "法", + "学", + "如", + "都", + "同", + "现", + "当", + "没", + "动", + "面", + "起", + "看", + "定", + "天", + "分", + "还", + "进", + "好", + "小", + "部", + "其", + "些", + "主", + "样", + "理", + "心", + "她", + "本", + "前", + "开", + "但", + "因", + "只", + "从", + "想", + "实", + ], + "Ukrainian": [ + "о", + "а", + "н", + "і", + "и", + "р", + "в", + "т", + "е", + "с", + "к", + "л", + "у", + "д", + "м", + "п", + "з", + "я", + "ь", + "б", + "г", + "й", + "ч", + "х", + "ц", + "ї", + ], + "Norwegian": [ + "e", + "r", + "n", + "t", + "a", + "s", + "i", + "o", + "l", + "d", + "g", + "k", + "m", + "v", + "f", + "p", + "u", + "b", + "h", + "å", + "y", + "j", + "ø", + "c", + "æ", + "w", + ], + "Finnish": [ + "a", + "i", + "n", + "t", + "e", + "s", + "l", + "o", + "u", + "k", + "ä", + "m", + "r", + "v", + "j", + "h", + "p", + "y", + "d", + "ö", + "g", + "c", + "b", + "f", + "w", + "z", + ], + "Vietnamese": [ + "n", + "h", + "t", + "i", + "c", + "g", + "a", + "o", + "u", + "m", + "l", + "r", + "à", + "đ", + "s", + "e", + "v", + "p", + "b", + "y", + "ư", + "d", + "á", + "k", + "ộ", + "ế", + ], + "Czech": [ + "o", + "e", + "a", + "n", + "t", + "s", + "i", + "l", + "v", + "r", + "k", + "d", + "u", + "m", + "p", + "í", + "c", + "h", + "z", + "á", + "y", + "j", + "b", + "ě", + "é", + "ř", + ], + "Hungarian": [ + "e", + "a", + "t", + "l", + "s", + "n", + "k", + "r", + "i", + "o", + "z", + "á", + "é", + "g", + "m", + "b", + "y", + "v", + "d", + "h", + "u", + "p", + "j", + "ö", + "f", + "c", + ], + "Korean": [ + "이", + "다", + "에", + "의", + "는", + "로", + "하", + "을", + "가", + "고", + "지", + "서", + "한", + "은", + "기", + "으", + "년", + "대", + "사", + "시", + "를", + "리", + "도", + "인", + "스", + "일", + ], + "Indonesian": [ + "a", + "n", + "e", + "i", + "r", + "t", + "u", + "s", + "d", + "k", + "m", + "l", + "g", + "p", + "b", + "o", + "h", + "y", + "j", + "c", + "w", + "f", + "v", + "z", + "x", + "q", + ], + "Turkish": [ + "a", + "e", + "i", + "n", + "r", + "l", + "ı", + "k", + "d", + "t", + "s", + "m", + "y", + "u", + "o", + "b", + "ü", + "ş", + "v", + "g", + "z", + "h", + "c", + "p", + "ç", + "ğ", + ], + "Romanian": [ + "e", + "i", + "a", + "r", + "n", + "t", + "u", + "l", + "o", + "c", + "s", + "d", + "p", + "m", + "ă", + "f", + "v", + "î", + "g", + "b", + "ș", + "ț", + "z", + "h", + "â", + "j", + ], + "Farsi": [ + "ا", + "ی", + "ر", + "د", + "ن", + "ه", + "و", + "م", + "ت", + "ب", + "س", + "ل", + "ک", + "ش", + "ز", + "ف", + "گ", + "ع", + "خ", + "ق", + "ج", + "آ", + "پ", + "ح", + "ط", + "ص", + ], + "Arabic": [ + "ا", + "ل", + "ي", + "م", + "و", + "ن", + "ر", + "ت", + "ب", + "ة", + "ع", + "د", + "س", + "ف", + "ه", + "ك", + "ق", + "أ", + "ح", + "ج", + "ش", + "ط", + "ص", + "ى", + "خ", + "إ", + ], + "Danish": [ + "e", + "r", + "n", + "t", + "a", + "i", + "s", + "d", + "l", + "o", + "g", + "m", + "k", + "f", + "v", + "u", + "b", + "h", + "p", + "å", + "y", + "ø", + "æ", + "c", + "j", + "w", + ], + "Serbian": [ + "а", + "и", + "о", + "е", + "н", + "р", + "с", + "у", + "т", + "к", + "ј", + "в", + "д", + "м", + "п", + "л", + "г", + "з", + "б", + "a", + "i", + "e", + "o", + "n", + "ц", + "ш", + ], + "Lithuanian": [ + "i", + "a", + "s", + "o", + "r", + "e", + "t", + "n", + "u", + "k", + "m", + "l", + "p", + "v", + "d", + "j", + "g", + "ė", + "b", + "y", + "ų", + "š", + "ž", + "c", + "ą", + "į", + ], + "Slovene": [ + "e", + "a", + "i", + "o", + "n", + "r", + "s", + "l", + "t", + "j", + "v", + "k", + "d", + "p", + "m", + "u", + "z", + "b", + "g", + "h", + "č", + "c", + "š", + "ž", + "f", + "y", + ], + "Slovak": [ + "o", + "a", + "e", + "n", + "i", + "r", + "v", + "t", + "s", + "l", + "k", + "d", + "m", + "p", + "u", + "c", + "h", + "j", + "b", + "z", + "á", + "y", + "ý", + "í", + "č", + "é", + ], + "Hebrew": [ + "י", + "ו", + "ה", + "ל", + "ר", + "ב", + "ת", + "מ", + "א", + "ש", + "נ", + "ע", + "ם", + "ד", + "ק", + "ח", + "פ", + "ס", + "כ", + "ג", + "ט", + "צ", + "ן", + "ז", + "ך", + ], + "Bulgarian": [ + "а", + "и", + "о", + "е", + "н", + "т", + "р", + "с", + "в", + "л", + "к", + "д", + "п", + "м", + "з", + "г", + "я", + "ъ", + "у", + "б", + "ч", + "ц", + "й", + "ж", + "щ", + "х", + ], + "Croatian": [ + "a", + "i", + "o", + "e", + "n", + "r", + "j", + "s", + "t", + "u", + "k", + "l", + "v", + "d", + "m", + "p", + "g", + "z", + "b", + "c", + "č", + "h", + "š", + "ž", + "ć", + "f", + ], + "Hindi": [ + "क", + "र", + "स", + "न", + "त", + "म", + "ह", + "प", + "य", + "ल", + "व", + "ज", + "द", + "ग", + "ब", + "श", + "ट", + "अ", + "ए", + "थ", + "भ", + "ड", + "च", + "ध", + "ष", + "इ", + ], + "Estonian": [ + "a", + "i", + "e", + "s", + "t", + "l", + "u", + "n", + "o", + "k", + "r", + "d", + "m", + "v", + "g", + "p", + "j", + "h", + "ä", + "b", + "õ", + "ü", + "f", + "c", + "ö", + "y", + ], + "Thai": [ + "า", + "น", + "ร", + "อ", + "ก", + "เ", + "ง", + "ม", + "ย", + "ล", + "ว", + "ด", + "ท", + "ส", + "ต", + "ะ", + "ป", + "บ", + "ค", + "ห", + "แ", + "จ", + "พ", + "ช", + "ข", + "ใ", + ], + "Greek": [ + "α", + "τ", + "ο", + "ι", + "ε", + "ν", + "ρ", + "σ", + "κ", + "η", + "π", + "ς", + "υ", + "μ", + "λ", + "ί", + "ό", + "ά", + "γ", + "έ", + "δ", + "ή", + "ω", + "χ", + "θ", + "ύ", + ], + "Tamil": [ + "க", + "த", + "ப", + "ட", + "ர", + "ம", + "ல", + "ன", + "வ", + "ற", + "ய", + "ள", + "ச", + "ந", + "இ", + "ண", + "அ", + "ஆ", + "ழ", + "ங", + "எ", + "உ", + "ஒ", + "ஸ", + ], + "Kazakh": [ + "а", + "ы", + "е", + "н", + "т", + "р", + "л", + "і", + "д", + "с", + "м", + "қ", + "к", + "о", + "б", + "и", + "у", + "ғ", + "ж", + "ң", + "з", + "ш", + "й", + "п", + "г", + "ө", + ], +} diff --git a/lib/charset_normalizer/assets/__pycache__/__init__.cpython-39.pyc b/lib/charset_normalizer/assets/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d2913f4977775da7c85f07045a8e19fd88b3f7c GIT binary patch literal 9549 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 List[str]: + """ + Return associated unicode ranges in a single byte code page. + """ + if is_multi_byte_encoding(iana_name): + raise IOError("Function not supported on multi-byte code page") + + decoder = importlib.import_module( + "encodings.{}".format(iana_name) + ).IncrementalDecoder + + p: IncrementalDecoder = decoder(errors="ignore") + seen_ranges: Dict[str, int] = {} + character_count: int = 0 + + for i in range(0x40, 0xFF): + chunk: str = p.decode(bytes([i])) + + if chunk: + character_range: Optional[str] = unicode_range(chunk) + + if character_range is None: + continue + + if is_unicode_range_secondary(character_range) is False: + if character_range not in seen_ranges: + seen_ranges[character_range] = 0 + seen_ranges[character_range] += 1 + character_count += 1 + + return sorted( + [ + character_range + for character_range in seen_ranges + if seen_ranges[character_range] / character_count >= 0.15 + ] + ) + + +def unicode_range_languages(primary_range: str) -> List[str]: + """ + Return inferred languages used with a unicode range. + """ + languages: List[str] = [] + + for language, characters in FREQUENCIES.items(): + for character in characters: + if unicode_range(character) == primary_range: + languages.append(language) + break + + return languages + + +@lru_cache() +def encoding_languages(iana_name: str) -> List[str]: + """ + Single-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + unicode_ranges: List[str] = encoding_unicode_range(iana_name) + primary_range: Optional[str] = None + + for specified_range in unicode_ranges: + if "Latin" not in specified_range: + primary_range = specified_range + break + + if primary_range is None: + return ["Latin Based"] + + return unicode_range_languages(primary_range) + + +@lru_cache() +def mb_encoding_languages(iana_name: str) -> List[str]: + """ + Multi-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + if ( + iana_name.startswith("shift_") + or iana_name.startswith("iso2022_jp") + or iana_name.startswith("euc_j") + or iana_name == "cp932" + ): + return ["Japanese"] + if iana_name.startswith("gb") or iana_name in ZH_NAMES: + return ["Chinese"] + if iana_name.startswith("iso2022_kr") or iana_name in KO_NAMES: + return ["Korean"] + + return [] + + +@lru_cache(maxsize=LANGUAGE_SUPPORTED_COUNT) +def get_target_features(language: str) -> Tuple[bool, bool]: + """ + Determine main aspects from a supported language if it contains accents and if is pure Latin. + """ + target_have_accents: bool = False + target_pure_latin: bool = True + + for character in FREQUENCIES[language]: + if not target_have_accents and is_accentuated(character): + target_have_accents = True + if target_pure_latin and is_latin(character) is False: + target_pure_latin = False + + return target_have_accents, target_pure_latin + + +def alphabet_languages( + characters: List[str], ignore_non_latin: bool = False +) -> List[str]: + """ + Return associated languages associated to given characters. + """ + languages: List[Tuple[str, float]] = [] + + source_have_accents = any(is_accentuated(character) for character in characters) + + for language, language_characters in FREQUENCIES.items(): + target_have_accents, target_pure_latin = get_target_features(language) + + if ignore_non_latin and target_pure_latin is False: + continue + + if target_have_accents is False and source_have_accents: + continue + + character_count: int = len(language_characters) + + character_match_count: int = len( + [c for c in language_characters if c in characters] + ) + + ratio: float = character_match_count / character_count + + if ratio >= 0.2: + languages.append((language, ratio)) + + languages = sorted(languages, key=lambda x: x[1], reverse=True) + + return [compatible_language[0] for compatible_language in languages] + + +def characters_popularity_compare( + language: str, ordered_characters: List[str] +) -> float: + """ + Determine if a ordered characters list (by occurrence from most appearance to rarest) match a particular language. + The result is a ratio between 0. (absolutely no correspondence) and 1. (near perfect fit). + Beware that is function is not strict on the match in order to ease the detection. (Meaning close match is 1.) + """ + if language not in FREQUENCIES: + raise ValueError("{} not available".format(language)) + + character_approved_count: int = 0 + FREQUENCIES_language_set = set(FREQUENCIES[language]) + + ordered_characters_count: int = len(ordered_characters) + target_language_characters_count: int = len(FREQUENCIES[language]) + + large_alphabet: bool = target_language_characters_count > 26 + + for character, character_rank in zip( + ordered_characters, range(0, ordered_characters_count) + ): + if character not in FREQUENCIES_language_set: + continue + + character_rank_in_language: int = FREQUENCIES[language].index(character) + expected_projection_ratio: float = ( + target_language_characters_count / ordered_characters_count + ) + character_rank_projection: int = int(character_rank * expected_projection_ratio) + + if ( + large_alphabet is False + and abs(character_rank_projection - character_rank_in_language) > 4 + ): + continue + + if ( + large_alphabet is True + and abs(character_rank_projection - character_rank_in_language) + < target_language_characters_count / 3 + ): + character_approved_count += 1 + continue + + characters_before_source: List[str] = FREQUENCIES[language][ + 0:character_rank_in_language + ] + characters_after_source: List[str] = FREQUENCIES[language][ + character_rank_in_language: + ] + characters_before: List[str] = ordered_characters[0:character_rank] + characters_after: List[str] = ordered_characters[character_rank:] + + before_match_count: int = len( + set(characters_before) & set(characters_before_source) + ) + + after_match_count: int = len( + set(characters_after) & set(characters_after_source) + ) + + if len(characters_before_source) == 0 and before_match_count <= 4: + character_approved_count += 1 + continue + + if len(characters_after_source) == 0 and after_match_count <= 4: + character_approved_count += 1 + continue + + if ( + before_match_count / len(characters_before_source) >= 0.4 + or after_match_count / len(characters_after_source) >= 0.4 + ): + character_approved_count += 1 + continue + + return character_approved_count / len(ordered_characters) + + +def alpha_unicode_split(decoded_sequence: str) -> List[str]: + """ + Given a decoded text sequence, return a list of str. Unicode range / alphabet separation. + Ex. a text containing English/Latin with a bit a Hebrew will return two items in the resulting list; + One containing the latin letters and the other hebrew. + """ + layers: Dict[str, str] = {} + + for character in decoded_sequence: + if character.isalpha() is False: + continue + + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + continue + + layer_target_range: Optional[str] = None + + for discovered_range in layers: + if ( + is_suspiciously_successive_range(discovered_range, character_range) + is False + ): + layer_target_range = discovered_range + break + + if layer_target_range is None: + layer_target_range = character_range + + if layer_target_range not in layers: + layers[layer_target_range] = character.lower() + continue + + layers[layer_target_range] += character.lower() + + return list(layers.values()) + + +def merge_coherence_ratios(results: List[CoherenceMatches]) -> CoherenceMatches: + """ + This function merge results previously given by the function coherence_ratio. + The return type is the same as coherence_ratio. + """ + per_language_ratios: Dict[str, List[float]] = {} + for result in results: + for sub_result in result: + language, ratio = sub_result + if language not in per_language_ratios: + per_language_ratios[language] = [ratio] + continue + per_language_ratios[language].append(ratio) + + merge = [ + ( + language, + round( + sum(per_language_ratios[language]) / len(per_language_ratios[language]), + 4, + ), + ) + for language in per_language_ratios + ] + + return sorted(merge, key=lambda x: x[1], reverse=True) + + +def filter_alt_coherence_matches(results: CoherenceMatches) -> CoherenceMatches: + """ + We shall NOT return "English—" in CoherenceMatches because it is an alternative + of "English". This function only keeps the best match and remove the em-dash in it. + """ + index_results: Dict[str, List[float]] = dict() + + for result in results: + language, ratio = result + no_em_name: str = language.replace("—", "") + + if no_em_name not in index_results: + index_results[no_em_name] = [] + + index_results[no_em_name].append(ratio) + + if any(len(index_results[e]) > 1 for e in index_results): + filtered_results: CoherenceMatches = [] + + for language in index_results: + filtered_results.append((language, max(index_results[language]))) + + return filtered_results + + return results + + +@lru_cache(maxsize=2048) +def coherence_ratio( + decoded_sequence: str, threshold: float = 0.1, lg_inclusion: Optional[str] = None +) -> CoherenceMatches: + """ + Detect ANY language that can be identified in given sequence. The sequence will be analysed by layers. + A layer = Character extraction by alphabets/ranges. + """ + + results: List[Tuple[str, float]] = [] + ignore_non_latin: bool = False + + sufficient_match_count: int = 0 + + lg_inclusion_list = lg_inclusion.split(",") if lg_inclusion is not None else [] + if "Latin Based" in lg_inclusion_list: + ignore_non_latin = True + lg_inclusion_list.remove("Latin Based") + + for layer in alpha_unicode_split(decoded_sequence): + sequence_frequencies: TypeCounter[str] = Counter(layer) + most_common = sequence_frequencies.most_common() + + character_count: int = sum(o for c, o in most_common) + + if character_count <= TOO_SMALL_SEQUENCE: + continue + + popular_character_ordered: List[str] = [c for c, o in most_common] + + for language in lg_inclusion_list or alphabet_languages( + popular_character_ordered, ignore_non_latin + ): + ratio: float = characters_popularity_compare( + language, popular_character_ordered + ) + + if ratio < threshold: + continue + elif ratio >= 0.8: + sufficient_match_count += 1 + + results.append((language, round(ratio, 4))) + + if sufficient_match_count >= 3: + break + + return sorted( + filter_alt_coherence_matches(results), key=lambda x: x[1], reverse=True + ) diff --git a/lib/charset_normalizer/cli/__init__.py b/lib/charset_normalizer/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/charset_normalizer/cli/__pycache__/__init__.cpython-39.pyc b/lib/charset_normalizer/cli/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5904cc884693f2e7eff131f5693f8ddd06d883b5 GIT binary patch literal 183 zcmYe~<>g`k0*|$lDIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o11z*(xTqIJKxa zCOEk$vmnMLwK%&ZzaS>Ppd`LHBQYhlD5fN}xWp*NCo?IgII|>Gw;(Y&J25@AI3_tG zv8Xt;Bt9>{C^s=DvnsVnKRG8;KR!M)FS8^*Uaz3?7Kcr4eoARhsvXGI&p^xo0QT-M A?f?J) literal 0 HcmV?d00001 diff --git a/lib/charset_normalizer/cli/__pycache__/normalizer.cpython-39.pyc b/lib/charset_normalizer/cli/__pycache__/normalizer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d715a403a869e6b0cbcb615aa4f6f5ae312c03a GIT binary patch literal 6482 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 bool: + """Ask a yes/no question via input() and return their answer. + + "question" is a string that is presented to the user. + "default" is the presumed answer if the user just hits . + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + The "answer" return value is True for "yes" or False for "no". + + Credit goes to (c) https://stackoverflow.com/questions/3041986/apt-command-line-interface-like-yes-no-input + """ + valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == "": + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") + + +def cli_detect(argv: Optional[List[str]] = None) -> int: + """ + CLI assistant using ARGV and ArgumentParser + :param argv: + :return: 0 if everything is fine, anything else equal trouble + """ + parser = argparse.ArgumentParser( + description="The Real First Universal Charset Detector. " + "Discover originating encoding used on text file. " + "Normalize text to unicode." + ) + + parser.add_argument( + "files", type=argparse.FileType("rb"), nargs="+", help="File(s) to be analysed" + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + default=False, + dest="verbose", + help="Display complementary information about file if any. " + "Stdout will contain logs about the detection process.", + ) + parser.add_argument( + "-a", + "--with-alternative", + action="store_true", + default=False, + dest="alternatives", + help="Output complementary possibilities if any. Top-level JSON WILL be a list.", + ) + parser.add_argument( + "-n", + "--normalize", + action="store_true", + default=False, + dest="normalize", + help="Permit to normalize input file. If not set, program does not write anything.", + ) + parser.add_argument( + "-m", + "--minimal", + action="store_true", + default=False, + dest="minimal", + help="Only output the charset detected to STDOUT. Disabling JSON output.", + ) + parser.add_argument( + "-r", + "--replace", + action="store_true", + default=False, + dest="replace", + help="Replace file when trying to normalize it instead of creating a new one.", + ) + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + dest="force", + help="Replace file without asking if you are sure, use this flag with caution.", + ) + parser.add_argument( + "-t", + "--threshold", + action="store", + default=0.2, + type=float, + dest="threshold", + help="Define a custom maximum amount of chaos allowed in decoded content. 0. <= chaos <= 1.", + ) + parser.add_argument( + "--version", + action="version", + version="Charset-Normalizer {} - Python {} - Unicode {} - SpeedUp {}".format( + __version__, + python_version(), + unidata_version, + "OFF" if md_module.__file__.lower().endswith(".py") else "ON", + ), + help="Show version information and exit.", + ) + + args = parser.parse_args(argv) + + if args.replace is True and args.normalize is False: + print("Use --replace in addition of --normalize only.", file=sys.stderr) + return 1 + + if args.force is True and args.replace is False: + print("Use --force in addition of --replace only.", file=sys.stderr) + return 1 + + if args.threshold < 0.0 or args.threshold > 1.0: + print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr) + return 1 + + x_ = [] + + for my_file in args.files: + matches = from_fp(my_file, threshold=args.threshold, explain=args.verbose) + + best_guess = matches.best() + + if best_guess is None: + print( + 'Unable to identify originating encoding for "{}". {}'.format( + my_file.name, + "Maybe try increasing maximum amount of chaos." + if args.threshold < 1.0 + else "", + ), + file=sys.stderr, + ) + x_.append( + CliDetectionResult( + abspath(my_file.name), + None, + [], + [], + "Unknown", + [], + False, + 1.0, + 0.0, + None, + True, + ) + ) + else: + x_.append( + CliDetectionResult( + abspath(my_file.name), + best_guess.encoding, + best_guess.encoding_aliases, + [ + cp + for cp in best_guess.could_be_from_charset + if cp != best_guess.encoding + ], + best_guess.language, + best_guess.alphabets, + best_guess.bom, + best_guess.percent_chaos, + best_guess.percent_coherence, + None, + True, + ) + ) + + if len(matches) > 1 and args.alternatives: + for el in matches: + if el != best_guess: + x_.append( + CliDetectionResult( + abspath(my_file.name), + el.encoding, + el.encoding_aliases, + [ + cp + for cp in el.could_be_from_charset + if cp != el.encoding + ], + el.language, + el.alphabets, + el.bom, + el.percent_chaos, + el.percent_coherence, + None, + False, + ) + ) + + if args.normalize is True: + if best_guess.encoding.startswith("utf") is True: + print( + '"{}" file does not need to be normalized, as it already came from unicode.'.format( + my_file.name + ), + file=sys.stderr, + ) + if my_file.closed is False: + my_file.close() + continue + + dir_path = dirname(realpath(my_file.name)) + file_name = basename(realpath(my_file.name)) + + o_: List[str] = file_name.split(".") + + if args.replace is False: + o_.insert(-1, best_guess.encoding) + if my_file.closed is False: + my_file.close() + elif ( + args.force is False + and query_yes_no( + 'Are you sure to normalize "{}" by replacing it ?'.format( + my_file.name + ), + "no", + ) + is False + ): + if my_file.closed is False: + my_file.close() + continue + + try: + x_[0].unicode_path = join(dir_path, ".".join(o_)) + + with open(x_[0].unicode_path, "w", encoding="utf-8") as fp: + fp.write(str(best_guess)) + except IOError as e: + print(str(e), file=sys.stderr) + if my_file.closed is False: + my_file.close() + return 2 + + if my_file.closed is False: + my_file.close() + + if args.minimal is False: + print( + dumps( + [el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__, + ensure_ascii=True, + indent=4, + ) + ) + else: + for my_file in args.files: + print( + ", ".join( + [ + el.encoding or "undefined" + for el in x_ + if el.path == abspath(my_file.name) + ] + ) + ) + + return 0 + + +if __name__ == "__main__": + cli_detect() diff --git a/lib/charset_normalizer/constant.py b/lib/charset_normalizer/constant.py new file mode 100644 index 0000000..3188108 --- /dev/null +++ b/lib/charset_normalizer/constant.py @@ -0,0 +1,495 @@ +from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE +from encodings.aliases import aliases +from re import IGNORECASE, compile as re_compile +from typing import Dict, List, Set, Union + +from .assets import FREQUENCIES + +# Contain for each eligible encoding a list of/item bytes SIG/BOM +ENCODING_MARKS: Dict[str, Union[bytes, List[bytes]]] = { + "utf_8": BOM_UTF8, + "utf_7": [ + b"\x2b\x2f\x76\x38", + b"\x2b\x2f\x76\x39", + b"\x2b\x2f\x76\x2b", + b"\x2b\x2f\x76\x2f", + b"\x2b\x2f\x76\x38\x2d", + ], + "gb18030": b"\x84\x31\x95\x33", + "utf_32": [BOM_UTF32_BE, BOM_UTF32_LE], + "utf_16": [BOM_UTF16_BE, BOM_UTF16_LE], +} + +TOO_SMALL_SEQUENCE: int = 32 +TOO_BIG_SEQUENCE: int = int(10e6) + +UTF8_MAXIMAL_ALLOCATION: int = 1112064 + +UNICODE_RANGES_COMBINED: Dict[str, range] = { + "Control character": range(31 + 1), + "Basic Latin": range(32, 127 + 1), + "Latin-1 Supplement": range(128, 255 + 1), + "Latin Extended-A": range(256, 383 + 1), + "Latin Extended-B": range(384, 591 + 1), + "IPA Extensions": range(592, 687 + 1), + "Spacing Modifier Letters": range(688, 767 + 1), + "Combining Diacritical Marks": range(768, 879 + 1), + "Greek and Coptic": range(880, 1023 + 1), + "Cyrillic": range(1024, 1279 + 1), + "Cyrillic Supplement": range(1280, 1327 + 1), + "Armenian": range(1328, 1423 + 1), + "Hebrew": range(1424, 1535 + 1), + "Arabic": range(1536, 1791 + 1), + "Syriac": range(1792, 1871 + 1), + "Arabic Supplement": range(1872, 1919 + 1), + "Thaana": range(1920, 1983 + 1), + "NKo": range(1984, 2047 + 1), + "Samaritan": range(2048, 2111 + 1), + "Mandaic": range(2112, 2143 + 1), + "Syriac Supplement": range(2144, 2159 + 1), + "Arabic Extended-A": range(2208, 2303 + 1), + "Devanagari": range(2304, 2431 + 1), + "Bengali": range(2432, 2559 + 1), + "Gurmukhi": range(2560, 2687 + 1), + "Gujarati": range(2688, 2815 + 1), + "Oriya": range(2816, 2943 + 1), + "Tamil": range(2944, 3071 + 1), + "Telugu": range(3072, 3199 + 1), + "Kannada": range(3200, 3327 + 1), + "Malayalam": range(3328, 3455 + 1), + "Sinhala": range(3456, 3583 + 1), + "Thai": range(3584, 3711 + 1), + "Lao": range(3712, 3839 + 1), + "Tibetan": range(3840, 4095 + 1), + "Myanmar": range(4096, 4255 + 1), + "Georgian": range(4256, 4351 + 1), + "Hangul Jamo": range(4352, 4607 + 1), + "Ethiopic": range(4608, 4991 + 1), + "Ethiopic Supplement": range(4992, 5023 + 1), + "Cherokee": range(5024, 5119 + 1), + "Unified Canadian Aboriginal Syllabics": range(5120, 5759 + 1), + "Ogham": range(5760, 5791 + 1), + "Runic": range(5792, 5887 + 1), + "Tagalog": range(5888, 5919 + 1), + "Hanunoo": range(5920, 5951 + 1), + "Buhid": range(5952, 5983 + 1), + "Tagbanwa": range(5984, 6015 + 1), + "Khmer": range(6016, 6143 + 1), + "Mongolian": range(6144, 6319 + 1), + "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6399 + 1), + "Limbu": range(6400, 6479 + 1), + "Tai Le": range(6480, 6527 + 1), + "New Tai Lue": range(6528, 6623 + 1), + "Khmer Symbols": range(6624, 6655 + 1), + "Buginese": range(6656, 6687 + 1), + "Tai Tham": range(6688, 6831 + 1), + "Combining Diacritical Marks Extended": range(6832, 6911 + 1), + "Balinese": range(6912, 7039 + 1), + "Sundanese": range(7040, 7103 + 1), + "Batak": range(7104, 7167 + 1), + "Lepcha": range(7168, 7247 + 1), + "Ol Chiki": range(7248, 7295 + 1), + "Cyrillic Extended C": range(7296, 7311 + 1), + "Sundanese Supplement": range(7360, 7375 + 1), + "Vedic Extensions": range(7376, 7423 + 1), + "Phonetic Extensions": range(7424, 7551 + 1), + "Phonetic Extensions Supplement": range(7552, 7615 + 1), + "Combining Diacritical Marks Supplement": range(7616, 7679 + 1), + "Latin Extended Additional": range(7680, 7935 + 1), + "Greek Extended": range(7936, 8191 + 1), + "General Punctuation": range(8192, 8303 + 1), + "Superscripts and Subscripts": range(8304, 8351 + 1), + "Currency Symbols": range(8352, 8399 + 1), + "Combining Diacritical Marks for Symbols": range(8400, 8447 + 1), + "Letterlike Symbols": range(8448, 8527 + 1), + "Number Forms": range(8528, 8591 + 1), + "Arrows": range(8592, 8703 + 1), + "Mathematical Operators": range(8704, 8959 + 1), + "Miscellaneous Technical": range(8960, 9215 + 1), + "Control Pictures": range(9216, 9279 + 1), + "Optical Character Recognition": range(9280, 9311 + 1), + "Enclosed Alphanumerics": range(9312, 9471 + 1), + "Box Drawing": range(9472, 9599 + 1), + "Block Elements": range(9600, 9631 + 1), + "Geometric Shapes": range(9632, 9727 + 1), + "Miscellaneous Symbols": range(9728, 9983 + 1), + "Dingbats": range(9984, 10175 + 1), + "Miscellaneous Mathematical Symbols-A": range(10176, 10223 + 1), + "Supplemental Arrows-A": range(10224, 10239 + 1), + "Braille Patterns": range(10240, 10495 + 1), + "Supplemental Arrows-B": range(10496, 10623 + 1), + "Miscellaneous Mathematical Symbols-B": range(10624, 10751 + 1), + "Supplemental Mathematical Operators": range(10752, 11007 + 1), + "Miscellaneous Symbols and Arrows": range(11008, 11263 + 1), + "Glagolitic": range(11264, 11359 + 1), + "Latin Extended-C": range(11360, 11391 + 1), + "Coptic": range(11392, 11519 + 1), + "Georgian Supplement": range(11520, 11567 + 1), + "Tifinagh": range(11568, 11647 + 1), + "Ethiopic Extended": range(11648, 11743 + 1), + "Cyrillic Extended-A": range(11744, 11775 + 1), + "Supplemental Punctuation": range(11776, 11903 + 1), + "CJK Radicals Supplement": range(11904, 12031 + 1), + "Kangxi Radicals": range(12032, 12255 + 1), + "Ideographic Description Characters": range(12272, 12287 + 1), + "CJK Symbols and Punctuation": range(12288, 12351 + 1), + "Hiragana": range(12352, 12447 + 1), + "Katakana": range(12448, 12543 + 1), + "Bopomofo": range(12544, 12591 + 1), + "Hangul Compatibility Jamo": range(12592, 12687 + 1), + "Kanbun": range(12688, 12703 + 1), + "Bopomofo Extended": range(12704, 12735 + 1), + "CJK Strokes": range(12736, 12783 + 1), + "Katakana Phonetic Extensions": range(12784, 12799 + 1), + "Enclosed CJK Letters and Months": range(12800, 13055 + 1), + "CJK Compatibility": range(13056, 13311 + 1), + "CJK Unified Ideographs Extension A": range(13312, 19903 + 1), + "Yijing Hexagram Symbols": range(19904, 19967 + 1), + "CJK Unified Ideographs": range(19968, 40959 + 1), + "Yi Syllables": range(40960, 42127 + 1), + "Yi Radicals": range(42128, 42191 + 1), + "Lisu": range(42192, 42239 + 1), + "Vai": range(42240, 42559 + 1), + "Cyrillic Extended-B": range(42560, 42655 + 1), + "Bamum": range(42656, 42751 + 1), + "Modifier Tone Letters": range(42752, 42783 + 1), + "Latin Extended-D": range(42784, 43007 + 1), + "Syloti Nagri": range(43008, 43055 + 1), + "Common Indic Number Forms": range(43056, 43071 + 1), + "Phags-pa": range(43072, 43135 + 1), + "Saurashtra": range(43136, 43231 + 1), + "Devanagari Extended": range(43232, 43263 + 1), + "Kayah Li": range(43264, 43311 + 1), + "Rejang": range(43312, 43359 + 1), + "Hangul Jamo Extended-A": range(43360, 43391 + 1), + "Javanese": range(43392, 43487 + 1), + "Myanmar Extended-B": range(43488, 43519 + 1), + "Cham": range(43520, 43615 + 1), + "Myanmar Extended-A": range(43616, 43647 + 1), + "Tai Viet": range(43648, 43743 + 1), + "Meetei Mayek Extensions": range(43744, 43775 + 1), + "Ethiopic Extended-A": range(43776, 43823 + 1), + "Latin Extended-E": range(43824, 43887 + 1), + "Cherokee Supplement": range(43888, 43967 + 1), + "Meetei Mayek": range(43968, 44031 + 1), + "Hangul Syllables": range(44032, 55215 + 1), + "Hangul Jamo Extended-B": range(55216, 55295 + 1), + "High Surrogates": range(55296, 56191 + 1), + "High Private Use Surrogates": range(56192, 56319 + 1), + "Low Surrogates": range(56320, 57343 + 1), + "Private Use Area": range(57344, 63743 + 1), + "CJK Compatibility Ideographs": range(63744, 64255 + 1), + "Alphabetic Presentation Forms": range(64256, 64335 + 1), + "Arabic Presentation Forms-A": range(64336, 65023 + 1), + "Variation Selectors": range(65024, 65039 + 1), + "Vertical Forms": range(65040, 65055 + 1), + "Combining Half Marks": range(65056, 65071 + 1), + "CJK Compatibility Forms": range(65072, 65103 + 1), + "Small Form Variants": range(65104, 65135 + 1), + "Arabic Presentation Forms-B": range(65136, 65279 + 1), + "Halfwidth and Fullwidth Forms": range(65280, 65519 + 1), + "Specials": range(65520, 65535 + 1), + "Linear B Syllabary": range(65536, 65663 + 1), + "Linear B Ideograms": range(65664, 65791 + 1), + "Aegean Numbers": range(65792, 65855 + 1), + "Ancient Greek Numbers": range(65856, 65935 + 1), + "Ancient Symbols": range(65936, 65999 + 1), + "Phaistos Disc": range(66000, 66047 + 1), + "Lycian": range(66176, 66207 + 1), + "Carian": range(66208, 66271 + 1), + "Coptic Epact Numbers": range(66272, 66303 + 1), + "Old Italic": range(66304, 66351 + 1), + "Gothic": range(66352, 66383 + 1), + "Old Permic": range(66384, 66431 + 1), + "Ugaritic": range(66432, 66463 + 1), + "Old Persian": range(66464, 66527 + 1), + "Deseret": range(66560, 66639 + 1), + "Shavian": range(66640, 66687 + 1), + "Osmanya": range(66688, 66735 + 1), + "Osage": range(66736, 66815 + 1), + "Elbasan": range(66816, 66863 + 1), + "Caucasian Albanian": range(66864, 66927 + 1), + "Linear A": range(67072, 67455 + 1), + "Cypriot Syllabary": range(67584, 67647 + 1), + "Imperial Aramaic": range(67648, 67679 + 1), + "Palmyrene": range(67680, 67711 + 1), + "Nabataean": range(67712, 67759 + 1), + "Hatran": range(67808, 67839 + 1), + "Phoenician": range(67840, 67871 + 1), + "Lydian": range(67872, 67903 + 1), + "Meroitic Hieroglyphs": range(67968, 67999 + 1), + "Meroitic Cursive": range(68000, 68095 + 1), + "Kharoshthi": range(68096, 68191 + 1), + "Old South Arabian": range(68192, 68223 + 1), + "Old North Arabian": range(68224, 68255 + 1), + "Manichaean": range(68288, 68351 + 1), + "Avestan": range(68352, 68415 + 1), + "Inscriptional Parthian": range(68416, 68447 + 1), + "Inscriptional Pahlavi": range(68448, 68479 + 1), + "Psalter Pahlavi": range(68480, 68527 + 1), + "Old Turkic": range(68608, 68687 + 1), + "Old Hungarian": range(68736, 68863 + 1), + "Rumi Numeral Symbols": range(69216, 69247 + 1), + "Brahmi": range(69632, 69759 + 1), + "Kaithi": range(69760, 69839 + 1), + "Sora Sompeng": range(69840, 69887 + 1), + "Chakma": range(69888, 69967 + 1), + "Mahajani": range(69968, 70015 + 1), + "Sharada": range(70016, 70111 + 1), + "Sinhala Archaic Numbers": range(70112, 70143 + 1), + "Khojki": range(70144, 70223 + 1), + "Multani": range(70272, 70319 + 1), + "Khudawadi": range(70320, 70399 + 1), + "Grantha": range(70400, 70527 + 1), + "Newa": range(70656, 70783 + 1), + "Tirhuta": range(70784, 70879 + 1), + "Siddham": range(71040, 71167 + 1), + "Modi": range(71168, 71263 + 1), + "Mongolian Supplement": range(71264, 71295 + 1), + "Takri": range(71296, 71375 + 1), + "Ahom": range(71424, 71487 + 1), + "Warang Citi": range(71840, 71935 + 1), + "Zanabazar Square": range(72192, 72271 + 1), + "Soyombo": range(72272, 72367 + 1), + "Pau Cin Hau": range(72384, 72447 + 1), + "Bhaiksuki": range(72704, 72815 + 1), + "Marchen": range(72816, 72895 + 1), + "Masaram Gondi": range(72960, 73055 + 1), + "Cuneiform": range(73728, 74751 + 1), + "Cuneiform Numbers and Punctuation": range(74752, 74879 + 1), + "Early Dynastic Cuneiform": range(74880, 75087 + 1), + "Egyptian Hieroglyphs": range(77824, 78895 + 1), + "Anatolian Hieroglyphs": range(82944, 83583 + 1), + "Bamum Supplement": range(92160, 92735 + 1), + "Mro": range(92736, 92783 + 1), + "Bassa Vah": range(92880, 92927 + 1), + "Pahawh Hmong": range(92928, 93071 + 1), + "Miao": range(93952, 94111 + 1), + "Ideographic Symbols and Punctuation": range(94176, 94207 + 1), + "Tangut": range(94208, 100351 + 1), + "Tangut Components": range(100352, 101119 + 1), + "Kana Supplement": range(110592, 110847 + 1), + "Kana Extended-A": range(110848, 110895 + 1), + "Nushu": range(110960, 111359 + 1), + "Duployan": range(113664, 113823 + 1), + "Shorthand Format Controls": range(113824, 113839 + 1), + "Byzantine Musical Symbols": range(118784, 119039 + 1), + "Musical Symbols": range(119040, 119295 + 1), + "Ancient Greek Musical Notation": range(119296, 119375 + 1), + "Tai Xuan Jing Symbols": range(119552, 119647 + 1), + "Counting Rod Numerals": range(119648, 119679 + 1), + "Mathematical Alphanumeric Symbols": range(119808, 120831 + 1), + "Sutton SignWriting": range(120832, 121519 + 1), + "Glagolitic Supplement": range(122880, 122927 + 1), + "Mende Kikakui": range(124928, 125151 + 1), + "Adlam": range(125184, 125279 + 1), + "Arabic Mathematical Alphabetic Symbols": range(126464, 126719 + 1), + "Mahjong Tiles": range(126976, 127023 + 1), + "Domino Tiles": range(127024, 127135 + 1), + "Playing Cards": range(127136, 127231 + 1), + "Enclosed Alphanumeric Supplement": range(127232, 127487 + 1), + "Enclosed Ideographic Supplement": range(127488, 127743 + 1), + "Miscellaneous Symbols and Pictographs": range(127744, 128511 + 1), + "Emoticons range(Emoji)": range(128512, 128591 + 1), + "Ornamental Dingbats": range(128592, 128639 + 1), + "Transport and Map Symbols": range(128640, 128767 + 1), + "Alchemical Symbols": range(128768, 128895 + 1), + "Geometric Shapes Extended": range(128896, 129023 + 1), + "Supplemental Arrows-C": range(129024, 129279 + 1), + "Supplemental Symbols and Pictographs": range(129280, 129535 + 1), + "CJK Unified Ideographs Extension B": range(131072, 173791 + 1), + "CJK Unified Ideographs Extension C": range(173824, 177983 + 1), + "CJK Unified Ideographs Extension D": range(177984, 178207 + 1), + "CJK Unified Ideographs Extension E": range(178208, 183983 + 1), + "CJK Unified Ideographs Extension F": range(183984, 191471 + 1), + "CJK Compatibility Ideographs Supplement": range(194560, 195103 + 1), + "Tags": range(917504, 917631 + 1), + "Variation Selectors Supplement": range(917760, 917999 + 1), +} + + +UNICODE_SECONDARY_RANGE_KEYWORD: List[str] = [ + "Supplement", + "Extended", + "Extensions", + "Modifier", + "Marks", + "Punctuation", + "Symbols", + "Forms", + "Operators", + "Miscellaneous", + "Drawing", + "Block", + "Shapes", + "Supplemental", + "Tags", +] + +RE_POSSIBLE_ENCODING_INDICATION = re_compile( + r"(?:(?:encoding)|(?:charset)|(?:coding))(?:[\:= ]{1,10})(?:[\"\']?)([a-zA-Z0-9\-_]+)(?:[\"\']?)", + IGNORECASE, +) + +IANA_SUPPORTED: List[str] = sorted( + filter( + lambda x: x.endswith("_codec") is False + and x not in {"rot_13", "tactis", "mbcs"}, + list(set(aliases.values())), + ) +) + +IANA_SUPPORTED_COUNT: int = len(IANA_SUPPORTED) + +# pre-computed code page that are similar using the function cp_similarity. +IANA_SUPPORTED_SIMILAR: Dict[str, List[str]] = { + "cp037": ["cp1026", "cp1140", "cp273", "cp500"], + "cp1026": ["cp037", "cp1140", "cp273", "cp500"], + "cp1125": ["cp866"], + "cp1140": ["cp037", "cp1026", "cp273", "cp500"], + "cp1250": ["iso8859_2"], + "cp1251": ["kz1048", "ptcp154"], + "cp1252": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1253": ["iso8859_7"], + "cp1254": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1257": ["iso8859_13"], + "cp273": ["cp037", "cp1026", "cp1140", "cp500"], + "cp437": ["cp850", "cp858", "cp860", "cp861", "cp862", "cp863", "cp865"], + "cp500": ["cp037", "cp1026", "cp1140", "cp273"], + "cp850": ["cp437", "cp857", "cp858", "cp865"], + "cp857": ["cp850", "cp858", "cp865"], + "cp858": ["cp437", "cp850", "cp857", "cp865"], + "cp860": ["cp437", "cp861", "cp862", "cp863", "cp865"], + "cp861": ["cp437", "cp860", "cp862", "cp863", "cp865"], + "cp862": ["cp437", "cp860", "cp861", "cp863", "cp865"], + "cp863": ["cp437", "cp860", "cp861", "cp862", "cp865"], + "cp865": ["cp437", "cp850", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863"], + "cp866": ["cp1125"], + "iso8859_10": ["iso8859_14", "iso8859_15", "iso8859_4", "iso8859_9", "latin_1"], + "iso8859_11": ["tis_620"], + "iso8859_13": ["cp1257"], + "iso8859_14": [ + "iso8859_10", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_15": [ + "cp1252", + "cp1254", + "iso8859_10", + "iso8859_14", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_16": [ + "iso8859_14", + "iso8859_15", + "iso8859_2", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_2": ["cp1250", "iso8859_16", "iso8859_4"], + "iso8859_3": ["iso8859_14", "iso8859_15", "iso8859_16", "iso8859_9", "latin_1"], + "iso8859_4": ["iso8859_10", "iso8859_2", "iso8859_9", "latin_1"], + "iso8859_7": ["cp1253"], + "iso8859_9": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "latin_1", + ], + "kz1048": ["cp1251", "ptcp154"], + "latin_1": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "iso8859_9", + ], + "mac_iceland": ["mac_roman", "mac_turkish"], + "mac_roman": ["mac_iceland", "mac_turkish"], + "mac_turkish": ["mac_iceland", "mac_roman"], + "ptcp154": ["cp1251", "kz1048"], + "tis_620": ["iso8859_11"], +} + + +CHARDET_CORRESPONDENCE: Dict[str, str] = { + "iso2022_kr": "ISO-2022-KR", + "iso2022_jp": "ISO-2022-JP", + "euc_kr": "EUC-KR", + "tis_620": "TIS-620", + "utf_32": "UTF-32", + "euc_jp": "EUC-JP", + "koi8_r": "KOI8-R", + "iso8859_1": "ISO-8859-1", + "iso8859_2": "ISO-8859-2", + "iso8859_5": "ISO-8859-5", + "iso8859_6": "ISO-8859-6", + "iso8859_7": "ISO-8859-7", + "iso8859_8": "ISO-8859-8", + "utf_16": "UTF-16", + "cp855": "IBM855", + "mac_cyrillic": "MacCyrillic", + "gb2312": "GB2312", + "gb18030": "GB18030", + "cp932": "CP932", + "cp866": "IBM866", + "utf_8": "utf-8", + "utf_8_sig": "UTF-8-SIG", + "shift_jis": "SHIFT_JIS", + "big5": "Big5", + "cp1250": "windows-1250", + "cp1251": "windows-1251", + "cp1252": "Windows-1252", + "cp1253": "windows-1253", + "cp1255": "windows-1255", + "cp1256": "windows-1256", + "cp1254": "Windows-1254", + "cp949": "CP949", +} + + +COMMON_SAFE_ASCII_CHARACTERS: Set[str] = { + "<", + ">", + "=", + ":", + "/", + "&", + ";", + "{", + "}", + "[", + "]", + ",", + "|", + '"', + "-", +} + + +KO_NAMES: Set[str] = {"johab", "cp949", "euc_kr"} +ZH_NAMES: Set[str] = {"big5", "cp950", "big5hkscs", "hz"} + +LANGUAGE_SUPPORTED_COUNT: int = len(FREQUENCIES) + +# Logging LEVEL below DEBUG +TRACE: int = 5 diff --git a/lib/charset_normalizer/legacy.py b/lib/charset_normalizer/legacy.py new file mode 100644 index 0000000..43aad21 --- /dev/null +++ b/lib/charset_normalizer/legacy.py @@ -0,0 +1,54 @@ +from typing import Any, Dict, Optional, Union +from warnings import warn + +from .api import from_bytes +from .constant import CHARDET_CORRESPONDENCE + + +def detect( + byte_str: bytes, should_rename_legacy: bool = False, **kwargs: Any +) -> Dict[str, Optional[Union[str, float]]]: + """ + chardet legacy method + Detect the encoding of the given byte string. It should be mostly backward-compatible. + Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it) + This function is deprecated and should be used to migrate your project easily, consult the documentation for + further information. Not planned for removal. + + :param byte_str: The byte sequence to examine. + :param should_rename_legacy: Should we rename legacy encodings + to their more modern equivalents? + """ + if len(kwargs): + warn( + f"charset-normalizer disregard arguments '{','.join(list(kwargs.keys()))}' in legacy function detect()" + ) + + if not isinstance(byte_str, (bytearray, bytes)): + raise TypeError( # pragma: nocover + "Expected object of type bytes or bytearray, got: " + "{0}".format(type(byte_str)) + ) + + if isinstance(byte_str, bytearray): + byte_str = bytes(byte_str) + + r = from_bytes(byte_str).best() + + encoding = r.encoding if r is not None else None + language = r.language if r is not None and r.language != "Unknown" else "" + confidence = 1.0 - r.chaos if r is not None else None + + # Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process + # but chardet does return 'utf-8-sig' and it is a valid codec name. + if r is not None and encoding == "utf_8" and r.bom: + encoding += "_sig" + + if should_rename_legacy is False and encoding in CHARDET_CORRESPONDENCE: + encoding = CHARDET_CORRESPONDENCE[encoding] + + return { + "encoding": encoding, + "language": language, + "confidence": confidence, + } diff --git a/lib/charset_normalizer/md.cp39-win_amd64.pyd b/lib/charset_normalizer/md.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..a49159057badf96a1f573224aaa08886b188dc7d GIT binary patch literal 10752 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 bool: + """ + Determine if given character should be fed in. + """ + raise NotImplementedError # pragma: nocover + + def feed(self, character: str) -> None: + """ + The main routine to be executed upon character. + Insert the logic in witch the text would be considered chaotic. + """ + raise NotImplementedError # pragma: nocover + + def reset(self) -> None: # pragma: no cover + """ + Permit to reset the plugin to the initial state. + """ + raise NotImplementedError + + @property + def ratio(self) -> float: + """ + Compute the chaos ratio based on what your feed() has seen. + Must NOT be lower than 0.; No restriction gt 0. + """ + raise NotImplementedError # pragma: nocover + + +class TooManySymbolOrPunctuationPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._punctuation_count: int = 0 + self._symbol_count: int = 0 + self._character_count: int = 0 + + self._last_printable_char: Optional[str] = None + self._frenzy_symbol_in_word: bool = False + + def eligible(self, character: str) -> bool: + return character.isprintable() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if ( + character != self._last_printable_char + and character not in COMMON_SAFE_ASCII_CHARACTERS + ): + if is_punctuation(character): + self._punctuation_count += 1 + elif ( + character.isdigit() is False + and is_symbol(character) + and is_emoticon(character) is False + ): + self._symbol_count += 2 + + self._last_printable_char = character + + def reset(self) -> None: # pragma: no cover + self._punctuation_count = 0 + self._character_count = 0 + self._symbol_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + ratio_of_punctuation: float = ( + self._punctuation_count + self._symbol_count + ) / self._character_count + + return ratio_of_punctuation if ratio_of_punctuation >= 0.3 else 0.0 + + +class TooManyAccentuatedPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._character_count: int = 0 + self._accentuated_count: int = 0 + + def eligible(self, character: str) -> bool: + return character.isalpha() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if is_accentuated(character): + self._accentuated_count += 1 + + def reset(self) -> None: # pragma: no cover + self._character_count = 0 + self._accentuated_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0 or self._character_count < 8: + return 0.0 + ratio_of_accentuation: float = self._accentuated_count / self._character_count + return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0 + + +class UnprintablePlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._unprintable_count: int = 0 + self._character_count: int = 0 + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if is_unprintable(character): + self._unprintable_count += 1 + self._character_count += 1 + + def reset(self) -> None: # pragma: no cover + self._unprintable_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._unprintable_count * 8) / self._character_count + + +class SuspiciousDuplicateAccentPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._successive_count: int = 0 + self._character_count: int = 0 + + self._last_latin_character: Optional[str] = None + + def eligible(self, character: str) -> bool: + return character.isalpha() and is_latin(character) + + def feed(self, character: str) -> None: + self._character_count += 1 + if ( + self._last_latin_character is not None + and is_accentuated(character) + and is_accentuated(self._last_latin_character) + ): + if character.isupper() and self._last_latin_character.isupper(): + self._successive_count += 1 + # Worse if its the same char duplicated with different accent. + if remove_accent(character) == remove_accent(self._last_latin_character): + self._successive_count += 1 + self._last_latin_character = character + + def reset(self) -> None: # pragma: no cover + self._successive_count = 0 + self._character_count = 0 + self._last_latin_character = None + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._successive_count * 2) / self._character_count + + +class SuspiciousRange(MessDetectorPlugin): + def __init__(self) -> None: + self._suspicious_successive_range_count: int = 0 + self._character_count: int = 0 + self._last_printable_seen: Optional[str] = None + + def eligible(self, character: str) -> bool: + return character.isprintable() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if ( + character.isspace() + or is_punctuation(character) + or character in COMMON_SAFE_ASCII_CHARACTERS + ): + self._last_printable_seen = None + return + + if self._last_printable_seen is None: + self._last_printable_seen = character + return + + unicode_range_a: Optional[str] = unicode_range(self._last_printable_seen) + unicode_range_b: Optional[str] = unicode_range(character) + + if is_suspiciously_successive_range(unicode_range_a, unicode_range_b): + self._suspicious_successive_range_count += 1 + + self._last_printable_seen = character + + def reset(self) -> None: # pragma: no cover + self._character_count = 0 + self._suspicious_successive_range_count = 0 + self._last_printable_seen = None + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + ratio_of_suspicious_range_usage: float = ( + self._suspicious_successive_range_count * 2 + ) / self._character_count + + if ratio_of_suspicious_range_usage < 0.1: + return 0.0 + + return ratio_of_suspicious_range_usage + + +class SuperWeirdWordPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._word_count: int = 0 + self._bad_word_count: int = 0 + self._foreign_long_count: int = 0 + + self._is_current_word_bad: bool = False + self._foreign_long_watch: bool = False + + self._character_count: int = 0 + self._bad_character_count: int = 0 + + self._buffer: str = "" + self._buffer_accent_count: int = 0 + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if character.isalpha(): + self._buffer += character + if is_accentuated(character): + self._buffer_accent_count += 1 + if ( + self._foreign_long_watch is False + and (is_latin(character) is False or is_accentuated(character)) + and is_cjk(character) is False + and is_hangul(character) is False + and is_katakana(character) is False + and is_hiragana(character) is False + and is_thai(character) is False + ): + self._foreign_long_watch = True + return + if not self._buffer: + return + if ( + character.isspace() or is_punctuation(character) or is_separator(character) + ) and self._buffer: + self._word_count += 1 + buffer_length: int = len(self._buffer) + + self._character_count += buffer_length + + if buffer_length >= 4: + if self._buffer_accent_count / buffer_length > 0.34: + self._is_current_word_bad = True + # Word/Buffer ending with a upper case accentuated letter are so rare, + # that we will consider them all as suspicious. Same weight as foreign_long suspicious. + if is_accentuated(self._buffer[-1]) and self._buffer[-1].isupper(): + self._foreign_long_count += 1 + self._is_current_word_bad = True + if buffer_length >= 24 and self._foreign_long_watch: + self._foreign_long_count += 1 + self._is_current_word_bad = True + + if self._is_current_word_bad: + self._bad_word_count += 1 + self._bad_character_count += len(self._buffer) + self._is_current_word_bad = False + + self._foreign_long_watch = False + self._buffer = "" + self._buffer_accent_count = 0 + elif ( + character not in {"<", ">", "-", "=", "~", "|", "_"} + and character.isdigit() is False + and is_symbol(character) + ): + self._is_current_word_bad = True + self._buffer += character + + def reset(self) -> None: # pragma: no cover + self._buffer = "" + self._is_current_word_bad = False + self._foreign_long_watch = False + self._bad_word_count = 0 + self._word_count = 0 + self._character_count = 0 + self._bad_character_count = 0 + self._foreign_long_count = 0 + + @property + def ratio(self) -> float: + if self._word_count <= 10 and self._foreign_long_count == 0: + return 0.0 + + return self._bad_character_count / self._character_count + + +class CjkInvalidStopPlugin(MessDetectorPlugin): + """ + GB(Chinese) based encoding often render the stop incorrectly when the content does not fit and + can be easily detected. Searching for the overuse of '丅' and '丄'. + """ + + def __init__(self) -> None: + self._wrong_stop_count: int = 0 + self._cjk_character_count: int = 0 + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if character in {"丅", "丄"}: + self._wrong_stop_count += 1 + return + if is_cjk(character): + self._cjk_character_count += 1 + + def reset(self) -> None: # pragma: no cover + self._wrong_stop_count = 0 + self._cjk_character_count = 0 + + @property + def ratio(self) -> float: + if self._cjk_character_count < 16: + return 0.0 + return self._wrong_stop_count / self._cjk_character_count + + +class ArchaicUpperLowerPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._buf: bool = False + + self._character_count_since_last_sep: int = 0 + + self._successive_upper_lower_count: int = 0 + self._successive_upper_lower_count_final: int = 0 + + self._character_count: int = 0 + + self._last_alpha_seen: Optional[str] = None + self._current_ascii_only: bool = True + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + is_concerned = character.isalpha() and is_case_variable(character) + chunk_sep = is_concerned is False + + if chunk_sep and self._character_count_since_last_sep > 0: + if ( + self._character_count_since_last_sep <= 64 + and character.isdigit() is False + and self._current_ascii_only is False + ): + self._successive_upper_lower_count_final += ( + self._successive_upper_lower_count + ) + + self._successive_upper_lower_count = 0 + self._character_count_since_last_sep = 0 + self._last_alpha_seen = None + self._buf = False + self._character_count += 1 + self._current_ascii_only = True + + return + + if self._current_ascii_only is True and is_ascii(character) is False: + self._current_ascii_only = False + + if self._last_alpha_seen is not None: + if (character.isupper() and self._last_alpha_seen.islower()) or ( + character.islower() and self._last_alpha_seen.isupper() + ): + if self._buf is True: + self._successive_upper_lower_count += 2 + self._buf = False + else: + self._buf = True + else: + self._buf = False + + self._character_count += 1 + self._character_count_since_last_sep += 1 + self._last_alpha_seen = character + + def reset(self) -> None: # pragma: no cover + self._character_count = 0 + self._character_count_since_last_sep = 0 + self._successive_upper_lower_count = 0 + self._successive_upper_lower_count_final = 0 + self._last_alpha_seen = None + self._buf = False + self._current_ascii_only = True + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return self._successive_upper_lower_count_final / self._character_count + + +@lru_cache(maxsize=1024) +def is_suspiciously_successive_range( + unicode_range_a: Optional[str], unicode_range_b: Optional[str] +) -> bool: + """ + Determine if two Unicode range seen next to each other can be considered as suspicious. + """ + if unicode_range_a is None or unicode_range_b is None: + return True + + if unicode_range_a == unicode_range_b: + return False + + if "Latin" in unicode_range_a and "Latin" in unicode_range_b: + return False + + if "Emoticons" in unicode_range_a or "Emoticons" in unicode_range_b: + return False + + # Latin characters can be accompanied with a combining diacritical mark + # eg. Vietnamese. + if ("Latin" in unicode_range_a or "Latin" in unicode_range_b) and ( + "Combining" in unicode_range_a or "Combining" in unicode_range_b + ): + return False + + keywords_range_a, keywords_range_b = unicode_range_a.split( + " " + ), unicode_range_b.split(" ") + + for el in keywords_range_a: + if el in UNICODE_SECONDARY_RANGE_KEYWORD: + continue + if el in keywords_range_b: + return False + + # Japanese Exception + range_a_jp_chars, range_b_jp_chars = ( + unicode_range_a + in ( + "Hiragana", + "Katakana", + ), + unicode_range_b in ("Hiragana", "Katakana"), + ) + if (range_a_jp_chars or range_b_jp_chars) and ( + "CJK" in unicode_range_a or "CJK" in unicode_range_b + ): + return False + if range_a_jp_chars and range_b_jp_chars: + return False + + if "Hangul" in unicode_range_a or "Hangul" in unicode_range_b: + if "CJK" in unicode_range_a or "CJK" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + # Chinese/Japanese use dedicated range for punctuation and/or separators. + if ("CJK" in unicode_range_a or "CJK" in unicode_range_b) or ( + unicode_range_a in ["Katakana", "Hiragana"] + and unicode_range_b in ["Katakana", "Hiragana"] + ): + if "Punctuation" in unicode_range_a or "Punctuation" in unicode_range_b: + return False + if "Forms" in unicode_range_a or "Forms" in unicode_range_b: + return False + + return True + + +@lru_cache(maxsize=2048) +def mess_ratio( + decoded_sequence: str, maximum_threshold: float = 0.2, debug: bool = False +) -> float: + """ + Compute a mess ratio given a decoded bytes sequence. The maximum threshold does stop the computation earlier. + """ + + detectors: List[MessDetectorPlugin] = [ + md_class() for md_class in MessDetectorPlugin.__subclasses__() + ] + + length: int = len(decoded_sequence) + 1 + + mean_mess_ratio: float = 0.0 + + if length < 512: + intermediary_mean_mess_ratio_calc: int = 32 + elif length <= 1024: + intermediary_mean_mess_ratio_calc = 64 + else: + intermediary_mean_mess_ratio_calc = 128 + + for character, index in zip(decoded_sequence + "\n", range(length)): + for detector in detectors: + if detector.eligible(character): + detector.feed(character) + + if ( + index > 0 and index % intermediary_mean_mess_ratio_calc == 0 + ) or index == length - 1: + mean_mess_ratio = sum(dt.ratio for dt in detectors) + + if mean_mess_ratio >= maximum_threshold: + break + + if debug: + logger = getLogger("charset_normalizer") + + logger.log( + TRACE, + "Mess-detector extended-analysis start. " + f"intermediary_mean_mess_ratio_calc={intermediary_mean_mess_ratio_calc} mean_mess_ratio={mean_mess_ratio} " + f"maximum_threshold={maximum_threshold}", + ) + + if len(decoded_sequence) > 16: + logger.log(TRACE, f"Starting with: {decoded_sequence[:16]}") + logger.log(TRACE, f"Ending with: {decoded_sequence[-16::]}") + + for dt in detectors: # pragma: nocover + logger.log(TRACE, f"{dt.__class__}: {dt.ratio}") + + return round(mean_mess_ratio, 3) diff --git a/lib/charset_normalizer/md__mypyc.cp39-win_amd64.pyd b/lib/charset_normalizer/md__mypyc.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..f76eb77d912c7fcfcba75dae7ff71ce8733078cb GIT binary patch literal 116736 zcmd?SdwdgB8aJMn1}Yj-l}HtkfK^cxss#lDnm{WPNu>xVDsmA-yn>p@9g0m|;}8}V zT-|jQSJw5qq9Rt*wdIZ=H&H=WMHknp6-9Ira3#O*_c=3@NiV>@zxThlpATftnR9ub z=REgwW~TJId9G$Imn#SVQYn|K3cvgd%Ku0HFE`8OYSm#`E7y|aKImAL75Sj!kny+N z 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 bool: + if not isinstance(other, CharsetMatch): + raise TypeError( + "__eq__ cannot be invoked on {} and {}.".format( + str(other.__class__), str(self.__class__) + ) + ) + return self.encoding == other.encoding and self.fingerprint == other.fingerprint + + def __lt__(self, other: object) -> bool: + """ + Implemented to make sorted available upon CharsetMatches items. + """ + if not isinstance(other, CharsetMatch): + raise ValueError + + chaos_difference: float = abs(self.chaos - other.chaos) + coherence_difference: float = abs(self.coherence - other.coherence) + + # Below 1% difference --> Use Coherence + if chaos_difference < 0.01 and coherence_difference > 0.02: + # When having a tough decision, use the result that decoded as many multi-byte as possible. + if chaos_difference == 0.0 and self.coherence == other.coherence: + return self.multi_byte_usage > other.multi_byte_usage + return self.coherence > other.coherence + + return self.chaos < other.chaos + + @property + def multi_byte_usage(self) -> float: + return 1.0 - len(str(self)) / len(self.raw) + + def __str__(self) -> str: + # Lazy Str Loading + if self._string is None: + self._string = str(self._payload, self._encoding, "strict") + return self._string + + def __repr__(self) -> str: + return "".format(self.encoding, self.fingerprint) + + def add_submatch(self, other: "CharsetMatch") -> None: + if not isinstance(other, CharsetMatch) or other == self: + raise ValueError( + "Unable to add instance <{}> as a submatch of a CharsetMatch".format( + other.__class__ + ) + ) + + other._string = None # Unload RAM usage; dirty trick. + self._leaves.append(other) + + @property + def encoding(self) -> str: + return self._encoding + + @property + def encoding_aliases(self) -> List[str]: + """ + Encoding name are known by many name, using this could help when searching for IBM855 when it's listed as CP855. + """ + also_known_as: List[str] = [] + for u, p in aliases.items(): + if self.encoding == u: + also_known_as.append(p) + elif self.encoding == p: + also_known_as.append(u) + return also_known_as + + @property + def bom(self) -> bool: + return self._has_sig_or_bom + + @property + def byte_order_mark(self) -> bool: + return self._has_sig_or_bom + + @property + def languages(self) -> List[str]: + """ + Return the complete list of possible languages found in decoded sequence. + Usually not really useful. Returned list may be empty even if 'language' property return something != 'Unknown'. + """ + return [e[0] for e in self._languages] + + @property + def language(self) -> str: + """ + Most probable language found in decoded sequence. If none were detected or inferred, the property will return + "Unknown". + """ + if not self._languages: + # Trying to infer the language based on the given encoding + # Its either English or we should not pronounce ourselves in certain cases. + if "ascii" in self.could_be_from_charset: + return "English" + + # doing it there to avoid circular import + from charset_normalizer.cd import encoding_languages, mb_encoding_languages + + languages = ( + mb_encoding_languages(self.encoding) + if is_multi_byte_encoding(self.encoding) + else encoding_languages(self.encoding) + ) + + if len(languages) == 0 or "Latin Based" in languages: + return "Unknown" + + return languages[0] + + return self._languages[0][0] + + @property + def chaos(self) -> float: + return self._mean_mess_ratio + + @property + def coherence(self) -> float: + if not self._languages: + return 0.0 + return self._languages[0][1] + + @property + def percent_chaos(self) -> float: + return round(self.chaos * 100, ndigits=3) + + @property + def percent_coherence(self) -> float: + return round(self.coherence * 100, ndigits=3) + + @property + def raw(self) -> bytes: + """ + Original untouched bytes. + """ + return self._payload + + @property + def submatch(self) -> List["CharsetMatch"]: + return self._leaves + + @property + def has_submatch(self) -> bool: + return len(self._leaves) > 0 + + @property + def alphabets(self) -> List[str]: + if self._unicode_ranges is not None: + return self._unicode_ranges + # list detected ranges + detected_ranges: List[Optional[str]] = [ + unicode_range(char) for char in str(self) + ] + # filter and sort + self._unicode_ranges = sorted(list({r for r in detected_ranges if r})) + return self._unicode_ranges + + @property + def could_be_from_charset(self) -> List[str]: + """ + The complete list of encoding that output the exact SAME str result and therefore could be the originating + encoding. + This list does include the encoding available in property 'encoding'. + """ + return [self._encoding] + [m.encoding for m in self._leaves] + + def output(self, encoding: str = "utf_8") -> bytes: + """ + Method to get re-encoded bytes payload using given target encoding. Default to UTF-8. + Any errors will be simply ignored by the encoder NOT replaced. + """ + if self._output_encoding is None or self._output_encoding != encoding: + self._output_encoding = encoding + self._output_payload = str(self).encode(encoding, "replace") + + return self._output_payload # type: ignore + + @property + def fingerprint(self) -> str: + """ + Retrieve the unique SHA256 computed using the transformed (re-encoded) payload. Not the original one. + """ + return sha256(self.output()).hexdigest() + + +class CharsetMatches: + """ + Container with every CharsetMatch items ordered by default from most probable to the less one. + Act like a list(iterable) but does not implements all related methods. + """ + + def __init__(self, results: Optional[List[CharsetMatch]] = None): + self._results: List[CharsetMatch] = sorted(results) if results else [] + + def __iter__(self) -> Iterator[CharsetMatch]: + yield from self._results + + def __getitem__(self, item: Union[int, str]) -> CharsetMatch: + """ + Retrieve a single item either by its position or encoding name (alias may be used here). + Raise KeyError upon invalid index or encoding not present in results. + """ + if isinstance(item, int): + return self._results[item] + if isinstance(item, str): + item = iana_name(item, False) + for result in self._results: + if item in result.could_be_from_charset: + return result + raise KeyError + + def __len__(self) -> int: + return len(self._results) + + def __bool__(self) -> bool: + return len(self._results) > 0 + + def append(self, item: CharsetMatch) -> None: + """ + Insert a single match. Will be inserted accordingly to preserve sort. + Can be inserted as a submatch. + """ + if not isinstance(item, CharsetMatch): + raise ValueError( + "Cannot append instance '{}' to CharsetMatches".format( + str(item.__class__) + ) + ) + # We should disable the submatch factoring when the input file is too heavy (conserve RAM usage) + if len(item.raw) <= TOO_BIG_SEQUENCE: + for match in self._results: + if match.fingerprint == item.fingerprint and match.chaos == item.chaos: + match.add_submatch(item) + return + self._results.append(item) + self._results = sorted(self._results) + + def best(self) -> Optional["CharsetMatch"]: + """ + Simply return the first match. Strict equivalent to matches[0]. + """ + if not self._results: + return None + return self._results[0] + + def first(self) -> Optional["CharsetMatch"]: + """ + Redundant method, call the method best(). Kept for BC reasons. + """ + return self.best() + + +CoherenceMatch = Tuple[str, float] +CoherenceMatches = List[CoherenceMatch] + + +class CliDetectionResult: + def __init__( + self, + path: str, + encoding: Optional[str], + encoding_aliases: List[str], + alternative_encodings: List[str], + language: str, + alphabets: List[str], + has_sig_or_bom: bool, + chaos: float, + coherence: float, + unicode_path: Optional[str], + is_preferred: bool, + ): + self.path: str = path + self.unicode_path: Optional[str] = unicode_path + self.encoding: Optional[str] = encoding + self.encoding_aliases: List[str] = encoding_aliases + self.alternative_encodings: List[str] = alternative_encodings + self.language: str = language + self.alphabets: List[str] = alphabets + self.has_sig_or_bom: bool = has_sig_or_bom + self.chaos: float = chaos + self.coherence: float = coherence + self.is_preferred: bool = is_preferred + + @property + def __dict__(self) -> Dict[str, Any]: # type: ignore + return { + "path": self.path, + "encoding": self.encoding, + "encoding_aliases": self.encoding_aliases, + "alternative_encodings": self.alternative_encodings, + "language": self.language, + "alphabets": self.alphabets, + "has_sig_or_bom": self.has_sig_or_bom, + "chaos": self.chaos, + "coherence": self.coherence, + "unicode_path": self.unicode_path, + "is_preferred": self.is_preferred, + } + + def to_json(self) -> str: + return dumps(self.__dict__, ensure_ascii=True, indent=4) diff --git a/lib/charset_normalizer/py.typed b/lib/charset_normalizer/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lib/charset_normalizer/utils.py b/lib/charset_normalizer/utils.py new file mode 100644 index 0000000..76eafc6 --- /dev/null +++ b/lib/charset_normalizer/utils.py @@ -0,0 +1,414 @@ +import importlib +import logging +import unicodedata +from codecs import IncrementalDecoder +from encodings.aliases import aliases +from functools import lru_cache +from re import findall +from typing import Generator, List, Optional, Set, Tuple, Union + +from _multibytecodec import MultibyteIncrementalDecoder + +from .constant import ( + ENCODING_MARKS, + IANA_SUPPORTED_SIMILAR, + RE_POSSIBLE_ENCODING_INDICATION, + UNICODE_RANGES_COMBINED, + UNICODE_SECONDARY_RANGE_KEYWORD, + UTF8_MAXIMAL_ALLOCATION, +) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_accentuated(character: str) -> bool: + try: + description: str = unicodedata.name(character) + except ValueError: + return False + return ( + "WITH GRAVE" in description + or "WITH ACUTE" in description + or "WITH CEDILLA" in description + or "WITH DIAERESIS" in description + or "WITH CIRCUMFLEX" in description + or "WITH TILDE" in description + ) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def remove_accent(character: str) -> str: + decomposed: str = unicodedata.decomposition(character) + if not decomposed: + return character + + codes: List[str] = decomposed.split(" ") + + return chr(int(codes[0], 16)) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def unicode_range(character: str) -> Optional[str]: + """ + Retrieve the Unicode range official name from a single character. + """ + character_ord: int = ord(character) + + for range_name, ord_range in UNICODE_RANGES_COMBINED.items(): + if character_ord in ord_range: + return range_name + + return None + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_latin(character: str) -> bool: + try: + description: str = unicodedata.name(character) + except ValueError: + return False + return "LATIN" in description + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_ascii(character: str) -> bool: + try: + character.encode("ascii") + except UnicodeEncodeError: + return False + return True + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_punctuation(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "P" in character_category: + return True + + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + return False + + return "Punctuation" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_symbol(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "S" in character_category or "N" in character_category: + return True + + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + return False + + return "Forms" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_emoticon(character: str) -> bool: + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + return False + + return "Emoticons" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_separator(character: str) -> bool: + if character.isspace() or character in {"|", "+", ",", ";", "<", ">"}: + return True + + character_category: str = unicodedata.category(character) + + return "Z" in character_category + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_case_variable(character: str) -> bool: + return character.islower() != character.isupper() + + +def is_private_use_only(character: str) -> bool: + character_category: str = unicodedata.category(character) + + return character_category == "Co" + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "CJK" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hiragana(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "HIRAGANA" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_katakana(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "KATAKANA" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hangul(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "HANGUL" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_thai(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "THAI" in character_name + + +@lru_cache(maxsize=len(UNICODE_RANGES_COMBINED)) +def is_unicode_range_secondary(range_name: str) -> bool: + return any(keyword in range_name for keyword in UNICODE_SECONDARY_RANGE_KEYWORD) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_unprintable(character: str) -> bool: + return ( + character.isspace() is False # includes \n \t \r \v + and character.isprintable() is False + and character != "\x1A" # Why? Its the ASCII substitute character. + and character != "\ufeff" # bug discovered in Python, + # Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space. + ) + + +def any_specified_encoding(sequence: bytes, search_zone: int = 4096) -> Optional[str]: + """ + Extract using ASCII-only decoder any specified encoding in the first n-bytes. + """ + if not isinstance(sequence, bytes): + raise TypeError + + seq_len: int = len(sequence) + + results: List[str] = findall( + RE_POSSIBLE_ENCODING_INDICATION, + sequence[: min(seq_len, search_zone)].decode("ascii", errors="ignore"), + ) + + if len(results) == 0: + return None + + for specified_encoding in results: + specified_encoding = specified_encoding.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if encoding_alias == specified_encoding: + return encoding_iana + if encoding_iana == specified_encoding: + return encoding_iana + + return None + + +@lru_cache(maxsize=128) +def is_multi_byte_encoding(name: str) -> bool: + """ + Verify is a specific encoding is a multi byte one based on it IANA name + """ + return name in { + "utf_8", + "utf_8_sig", + "utf_16", + "utf_16_be", + "utf_16_le", + "utf_32", + "utf_32_le", + "utf_32_be", + "utf_7", + } or issubclass( + importlib.import_module("encodings.{}".format(name)).IncrementalDecoder, + MultibyteIncrementalDecoder, + ) + + +def identify_sig_or_bom(sequence: bytes) -> Tuple[Optional[str], bytes]: + """ + Identify and extract SIG/BOM in given sequence. + """ + + for iana_encoding in ENCODING_MARKS: + marks: Union[bytes, List[bytes]] = ENCODING_MARKS[iana_encoding] + + if isinstance(marks, bytes): + marks = [marks] + + for mark in marks: + if sequence.startswith(mark): + return iana_encoding, mark + + return None, b"" + + +def should_strip_sig_or_bom(iana_encoding: str) -> bool: + return iana_encoding not in {"utf_16", "utf_32"} + + +def iana_name(cp_name: str, strict: bool = True) -> str: + cp_name = cp_name.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if cp_name in [encoding_alias, encoding_iana]: + return encoding_iana + + if strict: + raise ValueError("Unable to retrieve IANA for '{}'".format(cp_name)) + + return cp_name + + +def range_scan(decoded_sequence: str) -> List[str]: + ranges: Set[str] = set() + + for character in decoded_sequence: + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + continue + + ranges.add(character_range) + + return list(ranges) + + +def cp_similarity(iana_name_a: str, iana_name_b: str) -> float: + if is_multi_byte_encoding(iana_name_a) or is_multi_byte_encoding(iana_name_b): + return 0.0 + + decoder_a = importlib.import_module( + "encodings.{}".format(iana_name_a) + ).IncrementalDecoder + decoder_b = importlib.import_module( + "encodings.{}".format(iana_name_b) + ).IncrementalDecoder + + id_a: IncrementalDecoder = decoder_a(errors="ignore") + id_b: IncrementalDecoder = decoder_b(errors="ignore") + + character_match_count: int = 0 + + for i in range(255): + to_be_decoded: bytes = bytes([i]) + if id_a.decode(to_be_decoded) == id_b.decode(to_be_decoded): + character_match_count += 1 + + return character_match_count / 254 + + +def is_cp_similar(iana_name_a: str, iana_name_b: str) -> bool: + """ + Determine if two code page are at least 80% similar. IANA_SUPPORTED_SIMILAR dict was generated using + the function cp_similarity. + """ + return ( + iana_name_a in IANA_SUPPORTED_SIMILAR + and iana_name_b in IANA_SUPPORTED_SIMILAR[iana_name_a] + ) + + +def set_logging_handler( + name: str = "charset_normalizer", + level: int = logging.INFO, + format_string: str = "%(asctime)s | %(levelname)s | %(message)s", +) -> None: + logger = logging.getLogger(name) + logger.setLevel(level) + + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter(format_string)) + logger.addHandler(handler) + + +def cut_sequence_chunks( + sequences: bytes, + encoding_iana: str, + offsets: range, + chunk_size: int, + bom_or_sig_available: bool, + strip_sig_or_bom: bool, + sig_payload: bytes, + is_multi_byte_decoder: bool, + decoded_payload: Optional[str] = None, +) -> Generator[str, None, None]: + if decoded_payload and is_multi_byte_decoder is False: + for i in offsets: + chunk = decoded_payload[i : i + chunk_size] + if not chunk: + break + yield chunk + else: + for i in offsets: + chunk_end = i + chunk_size + if chunk_end > len(sequences) + 8: + continue + + cut_sequence = sequences[i : i + chunk_size] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode( + encoding_iana, + errors="ignore" if is_multi_byte_decoder else "strict", + ) + + # multi-byte bad cutting detector and adjustment + # not the cleanest way to perform that fix but clever enough for now. + if is_multi_byte_decoder and i > 0: + chunk_partial_size_chk: int = min(chunk_size, 16) + + if ( + decoded_payload + and chunk[:chunk_partial_size_chk] not in decoded_payload + ): + for j in range(i, i - 4, -1): + cut_sequence = sequences[j:chunk_end] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode(encoding_iana, errors="ignore") + + if chunk[:chunk_partial_size_chk] in decoded_payload: + break + + yield chunk diff --git a/lib/charset_normalizer/version.py b/lib/charset_normalizer/version.py new file mode 100644 index 0000000..b74c264 --- /dev/null +++ b/lib/charset_normalizer/version.py @@ -0,0 +1,6 @@ +""" +Expose version +""" + +__version__ = "3.1.0" +VERSION = __version__.split(".") diff --git a/lib/colorama-0.4.6.dist-info/INSTALLER b/lib/colorama-0.4.6.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/colorama-0.4.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/colorama-0.4.6.dist-info/METADATA b/lib/colorama-0.4.6.dist-info/METADATA new file mode 100644 index 0000000..a1b5c57 --- /dev/null +++ b/lib/colorama-0.4.6.dist-info/METADATA @@ -0,0 +1,441 @@ +Metadata-Version: 2.1 +Name: colorama +Version: 0.4.6 +Summary: Cross-platform colored terminal text. +Project-URL: Homepage, https://github.com/tartley/colorama +Author-email: Jonathan Hartley +License-File: LICENSE.txt +Keywords: ansi,color,colour,crossplatform,terminal,text,windows,xplatform +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Terminals +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 +Description-Content-Type: text/x-rst + +.. image:: https://img.shields.io/pypi/v/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/pyversions/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Supported Python versions + +.. image:: https://github.com/tartley/colorama/actions/workflows/test.yml/badge.svg + :target: https://github.com/tartley/colorama/actions/workflows/test.yml + :alt: Build Status + +Colorama +======== + +Makes ANSI escape character sequences (for producing colored terminal text and +cursor positioning) work under MS Windows. + +.. |donate| image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif + :target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2MZ9D2GMLYCUJ&item_name=Colorama¤cy_code=USD + :alt: Donate with Paypal + +`PyPI for releases `_ | +`Github for source `_ | +`Colorama for enterprise on Tidelift `_ + +If you find Colorama useful, please |donate| to the authors. Thank you! + +Installation +------------ + +Tested on CPython 2.7, 3.7, 3.8, 3.9 and 3.10 and Pypy 2.7 and 3.8. + +No requirements other than the standard library. + +.. code-block:: bash + + pip install colorama + # or + conda install -c anaconda colorama + +Description +----------- + +ANSI escape character sequences have long been used to produce colored terminal +text and cursor positioning on Unix and Macs. Colorama makes this work on +Windows, too, by wrapping ``stdout``, stripping ANSI sequences it finds (which +would appear as gobbledygook in the output), and converting them into the +appropriate win32 calls to modify the state of the terminal. On other platforms, +Colorama does nothing. + +This has the upshot of providing a simple cross-platform API for printing +colored terminal text from Python, and has the happy side-effect that existing +applications or libraries which use ANSI sequences to produce colored output on +Linux or Macs can now also work on Windows, simply by calling +``colorama.just_fix_windows_console()`` (since v0.4.6) or ``colorama.init()`` +(all versions, but may have other side-effects – see below). + +An alternative approach is to install ``ansi.sys`` on Windows machines, which +provides the same behaviour for all applications running in terminals. Colorama +is intended for situations where that isn't easy (e.g., maybe your app doesn't +have an installer.) + +Demo scripts in the source code repository print some colored text using +ANSI sequences. Compare their output under Gnome-terminal's built in ANSI +handling, versus on Windows Command-Prompt using Colorama: + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/ubuntu-demo.png + :width: 661 + :height: 357 + :alt: ANSI sequences on Ubuntu under gnome-terminal. + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/windows-demo.png + :width: 668 + :height: 325 + :alt: Same ANSI sequences on Windows, using Colorama. + +These screenshots show that, on Windows, Colorama does not support ANSI 'dim +text'; it looks the same as 'normal text'. + +Usage +----- + +Initialisation +.............. + +If the only thing you want from Colorama is to get ANSI escapes to work on +Windows, then run: + +.. code-block:: python + + from colorama import just_fix_windows_console + just_fix_windows_console() + +If you're on a recent version of Windows 10 or better, and your stdout/stderr +are pointing to a Windows console, then this will flip the magic configuration +switch to enable Windows' built-in ANSI support. + +If you're on an older version of Windows, and your stdout/stderr are pointing to +a Windows console, then this will wrap ``sys.stdout`` and/or ``sys.stderr`` in a +magic file object that intercepts ANSI escape sequences and issues the +appropriate Win32 calls to emulate them. + +In all other circumstances, it does nothing whatsoever. Basically the idea is +that this makes Windows act like Unix with respect to ANSI escape handling. + +It's safe to call this function multiple times. It's safe to call this function +on non-Windows platforms, but it won't do anything. It's safe to call this +function when one or both of your stdout/stderr are redirected to a file – it +won't do anything to those streams. + +Alternatively, you can use the older interface with more features (but also more +potential footguns): + +.. code-block:: python + + from colorama import init + init() + +This does the same thing as ``just_fix_windows_console``, except for the +following differences: + +- It's not safe to call ``init`` multiple times; you can end up with multiple + layers of wrapping and broken ANSI support. + +- Colorama will apply a heuristic to guess whether stdout/stderr support ANSI, + and if it thinks they don't, then it will wrap ``sys.stdout`` and + ``sys.stderr`` in a magic file object that strips out ANSI escape sequences + before printing them. This happens on all platforms, and can be convenient if + you want to write your code to emit ANSI escape sequences unconditionally, and + let Colorama decide whether they should actually be output. But note that + Colorama's heuristic is not particularly clever. + +- ``init`` also accepts explicit keyword args to enable/disable various + functionality – see below. + +To stop using Colorama before your program exits, simply call ``deinit()``. +This will restore ``stdout`` and ``stderr`` to their original values, so that +Colorama is disabled. To resume using Colorama again, call ``reinit()``; it is +cheaper than calling ``init()`` again (but does the same thing). + +Most users should depend on ``colorama >= 0.4.6``, and use +``just_fix_windows_console``. The old ``init`` interface will be supported +indefinitely for backwards compatibility, but we don't plan to fix any issues +with it, also for backwards compatibility. + +Colored Output +.............. + +Cross-platform printing of colored text can then be done using Colorama's +constant shorthand for ANSI escape sequences. These are deliberately +rudimentary, see below. + +.. code-block:: python + + from colorama import Fore, Back, Style + print(Fore.RED + 'some red text') + print(Back.GREEN + 'and with a green background') + print(Style.DIM + 'and in dim text') + print(Style.RESET_ALL) + print('back to normal now') + +...or simply by manually printing ANSI sequences from your own code: + +.. code-block:: python + + print('\033[31m' + 'some red text') + print('\033[39m') # and reset to default color + +...or, Colorama can be used in conjunction with existing ANSI libraries +such as the venerable `Termcolor `_ +the fabulous `Blessings `_, +or the incredible `_Rich `_. + +If you wish Colorama's Fore, Back and Style constants were more capable, +then consider using one of the above highly capable libraries to generate +colors, etc, and use Colorama just for its primary purpose: to convert +those ANSI sequences to also work on Windows: + +SIMILARLY, do not send PRs adding the generation of new ANSI types to Colorama. +We are only interested in converting ANSI codes to win32 API calls, not +shortcuts like the above to generate ANSI characters. + +.. code-block:: python + + from colorama import just_fix_windows_console + from termcolor import colored + + # use Colorama to make Termcolor work on Windows too + just_fix_windows_console() + + # then use Termcolor for all colored text output + print(colored('Hello, World!', 'green', 'on_red')) + +Available formatting constants are:: + + Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Style: DIM, NORMAL, BRIGHT, RESET_ALL + +``Style.RESET_ALL`` resets foreground, background, and brightness. Colorama will +perform this reset automatically on program exit. + +These are fairly well supported, but not part of the standard:: + + Fore: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX + Back: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX + +Cursor Positioning +.................. + +ANSI codes to reposition the cursor are supported. See ``demos/demo06.py`` for +an example of how to generate them. + +Init Keyword Args +................. + +``init()`` accepts some ``**kwargs`` to override default behaviour. + +init(autoreset=False): + If you find yourself repeatedly sending reset sequences to turn off color + changes at the end of every print, then ``init(autoreset=True)`` will + automate that: + + .. code-block:: python + + from colorama import init + init(autoreset=True) + print(Fore.RED + 'some red text') + print('automatically back to default color again') + +init(strip=None): + Pass ``True`` or ``False`` to override whether ANSI codes should be + stripped from the output. The default behaviour is to strip if on Windows + or if output is redirected (not a tty). + +init(convert=None): + Pass ``True`` or ``False`` to override whether to convert ANSI codes in the + output into win32 calls. The default behaviour is to convert if on Windows + and output is to a tty (terminal). + +init(wrap=True): + On Windows, Colorama works by replacing ``sys.stdout`` and ``sys.stderr`` + with proxy objects, which override the ``.write()`` method to do their work. + If this wrapping causes you problems, then this can be disabled by passing + ``init(wrap=False)``. The default behaviour is to wrap if ``autoreset`` or + ``strip`` or ``convert`` are True. + + When wrapping is disabled, colored printing on non-Windows platforms will + continue to work as normal. To do cross-platform colored output, you can + use Colorama's ``AnsiToWin32`` proxy directly: + + .. code-block:: python + + import sys + from colorama import init, AnsiToWin32 + init(wrap=False) + stream = AnsiToWin32(sys.stderr).stream + + # Python 2 + print >>stream, Fore.BLUE + 'blue text on stderr' + + # Python 3 + print(Fore.BLUE + 'blue text on stderr', file=stream) + +Recognised ANSI Sequences +......................... + +ANSI sequences generally take the form:: + + ESC [ ; ... + +Where ```` is an integer, and ```` is a single letter. Zero or +more params are passed to a ````. If no params are passed, it is +generally synonymous with passing a single zero. No spaces exist in the +sequence; they have been inserted here simply to read more easily. + +The only ANSI sequences that Colorama converts into win32 calls are:: + + ESC [ 0 m # reset all (colors and brightness) + ESC [ 1 m # bright + ESC [ 2 m # dim (looks same as normal brightness) + ESC [ 22 m # normal brightness + + # FOREGROUND: + ESC [ 30 m # black + ESC [ 31 m # red + ESC [ 32 m # green + ESC [ 33 m # yellow + ESC [ 34 m # blue + ESC [ 35 m # magenta + ESC [ 36 m # cyan + ESC [ 37 m # white + ESC [ 39 m # reset + + # BACKGROUND + ESC [ 40 m # black + ESC [ 41 m # red + ESC [ 42 m # green + ESC [ 43 m # yellow + ESC [ 44 m # blue + ESC [ 45 m # magenta + ESC [ 46 m # cyan + ESC [ 47 m # white + ESC [ 49 m # reset + + # cursor positioning + ESC [ y;x H # position cursor at x across, y down + ESC [ y;x f # position cursor at x across, y down + ESC [ n A # move cursor n lines up + ESC [ n B # move cursor n lines down + ESC [ n C # move cursor n characters forward + ESC [ n D # move cursor n characters backward + + # clear the screen + ESC [ mode J # clear the screen + + # clear the line + ESC [ mode K # clear the line + +Multiple numeric params to the ``'m'`` command can be combined into a single +sequence:: + + ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background + +All other ANSI sequences of the form ``ESC [ ; ... `` +are silently stripped from the output on Windows. + +Any other form of ANSI sequence, such as single-character codes or alternative +initial characters, are not recognised or stripped. It would be cool to add +them though. Let me know if it would be useful for you, via the Issues on +GitHub. + +Status & Known Problems +----------------------- + +I've personally only tested it on Windows XP (CMD, Console2), Ubuntu +(gnome-terminal, xterm), and OS X. + +Some valid ANSI sequences aren't recognised. + +If you're hacking on the code, see `README-hacking.md`_. ESPECIALLY, see the +explanation there of why we do not want PRs that allow Colorama to generate new +types of ANSI codes. + +See outstanding issues and wish-list: +https://github.com/tartley/colorama/issues + +If anything doesn't work for you, or doesn't do what you expected or hoped for, +I'd love to hear about it on that issues list, would be delighted by patches, +and would be happy to grant commit access to anyone who submits a working patch +or two. + +.. _README-hacking.md: README-hacking.md + +License +------- + +Copyright Jonathan Hartley & Arnon Yaari, 2013-2020. BSD 3-Clause license; see +LICENSE file. + +Professional support +-------------------- + +.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png + :alt: Tidelift + :target: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + +.. list-table:: + :widths: 10 100 + + * - |tideliftlogo| + - Professional support for colorama is available as part of the + `Tidelift Subscription`_. + Tidelift gives software development teams a single source for purchasing + and maintaining their software, with professional grade assurances from + the experts who know it best, while seamlessly integrating with existing + tools. + +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + +Thanks +------ + +See the CHANGELOG for more thanks! + +* Marc Schlaich (schlamar) for a ``setup.py`` fix for Python2.5. +* Marc Abramowitz, reported & fixed a crash on exit with closed ``stdout``, + providing a solution to issue #7's setuptools/distutils debate, + and other fixes. +* User 'eryksun', for guidance on correctly instantiating ``ctypes.windll``. +* Matthew McCormick for politely pointing out a longstanding crash on non-Win. +* Ben Hoyt, for a magnificent fix under 64-bit Windows. +* Jesse at Empty Square for submitting a fix for examples in the README. +* User 'jamessp', an observant documentation fix for cursor positioning. +* User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7 + fix. +* Julien Stuyck, for wisely suggesting Python3 compatible updates to README. +* Daniel Griffith for multiple fabulous patches. +* Oscar Lesta for a valuable fix to stop ANSI chars being sent to non-tty + output. +* Roger Binns, for many suggestions, valuable feedback, & bug reports. +* Tim Golden for thought and much appreciated feedback on the initial idea. +* User 'Zearin' for updates to the README file. +* John Szakmeister for adding support for light colors +* Charles Merriam for adding documentation to demos +* Jurko for a fix on 64-bit Windows CPython2.5 w/o ctypes +* Florian Bruhin for a fix when stdout or stderr are None +* Thomas Weininger for fixing ValueError on Windows +* Remi Rampin for better Github integration and fixes to the README file +* Simeon Visser for closing a file handle using 'with' and updating classifiers + to include Python 3.3 and 3.4 +* Andy Neff for fixing RESET of LIGHT_EX colors. +* Jonathan Hartley for the initial idea and implementation. diff --git a/lib/colorama-0.4.6.dist-info/RECORD b/lib/colorama-0.4.6.dist-info/RECORD new file mode 100644 index 0000000..1e4031e --- /dev/null +++ b/lib/colorama-0.4.6.dist-info/RECORD @@ -0,0 +1,31 @@ +colorama-0.4.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +colorama-0.4.6.dist-info/METADATA,sha256=e67SnrUMOym9sz_4TjF3vxvAV4T3aF7NyqRHHH3YEMw,17158 +colorama-0.4.6.dist-info/RECORD,, +colorama-0.4.6.dist-info/WHEEL,sha256=cdcF4Fbd0FPtw2EMIOwH-3rSOTUdTCeOSXRMD1iLUb8,105 +colorama-0.4.6.dist-info/licenses/LICENSE.txt,sha256=ysNcAmhuXQSlpxQL-zs25zrtSWZW6JEQLkKIhteTAxg,1491 +colorama/__init__.py,sha256=wePQA4U20tKgYARySLEC047ucNX-g8pRLpYBuiHlLb8,266 +colorama/__pycache__/__init__.cpython-39.pyc,, +colorama/__pycache__/ansi.cpython-39.pyc,, +colorama/__pycache__/ansitowin32.cpython-39.pyc,, +colorama/__pycache__/initialise.cpython-39.pyc,, +colorama/__pycache__/win32.cpython-39.pyc,, +colorama/__pycache__/winterm.cpython-39.pyc,, +colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522 +colorama/ansitowin32.py,sha256=vPNYa3OZbxjbuFyaVo0Tmhmy1FZ1lKMWCnT7odXpItk,11128 +colorama/initialise.py,sha256=-hIny86ClXo39ixh5iSCfUIa2f_h_bgKRDW7gqs-KLU,3325 +colorama/tests/__init__.py,sha256=MkgPAEzGQd-Rq0w0PZXSX2LadRWhUECcisJY8lSrm4Q,75 +colorama/tests/__pycache__/__init__.cpython-39.pyc,, +colorama/tests/__pycache__/ansi_test.cpython-39.pyc,, +colorama/tests/__pycache__/ansitowin32_test.cpython-39.pyc,, +colorama/tests/__pycache__/initialise_test.cpython-39.pyc,, +colorama/tests/__pycache__/isatty_test.cpython-39.pyc,, +colorama/tests/__pycache__/utils.cpython-39.pyc,, +colorama/tests/__pycache__/winterm_test.cpython-39.pyc,, +colorama/tests/ansi_test.py,sha256=FeViDrUINIZcr505PAxvU4AjXz1asEiALs9GXMhwRaE,2839 +colorama/tests/ansitowin32_test.py,sha256=RN7AIhMJ5EqDsYaCjVo-o4u8JzDD4ukJbmevWKS70rY,10678 +colorama/tests/initialise_test.py,sha256=BbPy-XfyHwJ6zKozuQOvNvQZzsx9vdb_0bYXn7hsBTc,6741 +colorama/tests/isatty_test.py,sha256=Pg26LRpv0yQDB5Ac-sxgVXG7hsA1NYvapFgApZfYzZg,1866 +colorama/tests/utils.py,sha256=1IIRylG39z5-dzq09R_ngufxyPZxgldNbrxKxUGwGKE,1079 +colorama/tests/winterm_test.py,sha256=qoWFPEjym5gm2RuMwpf3pOis3a5r_PJZFCzK254JL8A,3709 +colorama/win32.py,sha256=YQOKwMTwtGBbsY4dL5HYTvwTeP9wIQra5MvPNddpxZs,6181 +colorama/winterm.py,sha256=XCQFDHjPi6AHYNdZwy0tA02H-Jh48Jp-HvCjeLeLp3U,7134 diff --git a/lib/colorama-0.4.6.dist-info/WHEEL b/lib/colorama-0.4.6.dist-info/WHEEL new file mode 100644 index 0000000..d79189f --- /dev/null +++ b/lib/colorama-0.4.6.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.11.1 +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any diff --git a/lib/colorama-0.4.6.dist-info/licenses/LICENSE.txt b/lib/colorama-0.4.6.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..3105888 --- /dev/null +++ b/lib/colorama-0.4.6.dist-info/licenses/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2010 Jonathan Hartley +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holders, nor those of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/colorama/__init__.py b/lib/colorama/__init__.py new file mode 100644 index 0000000..383101c --- /dev/null +++ b/lib/colorama/__init__.py @@ -0,0 +1,7 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from .initialise import init, deinit, reinit, colorama_text, just_fix_windows_console +from .ansi import Fore, Back, Style, Cursor +from .ansitowin32 import AnsiToWin32 + +__version__ = '0.4.6' + diff --git a/lib/colorama/__pycache__/__init__.cpython-39.pyc b/lib/colorama/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6b0f73b59ee86125600dfe19290157ef23a5cf9 GIT binary patch literal 455 zcmYk2PfNov6u|o@?Yedqj}!a?Jve0(4v=e zSMX$>L-0X@8T|xu}x|Hex2|h?%6xSX%)lfguFX)7oGyXr{mN7-d0ihtdy+Huc=yS(dP436ZKqFROXs0Joe{N7M5$R_Db2`_PFN@#>@VI!b>qI4|&iR zLB9)cdIPRr0e?5>d%dt~w;8)r-WFnbXKs%5tFMBcV903TRR_E~htwR(by?_pL*}AT zdtblt1jf_%B;^zE+33Q#0izBhECL8kkOS?I%z{g2HQ2n~l4SQtmqxw4B8^b+3Xvwt zJkodyK6wA2ey6h;G9Gr?J`aYSMHcRe!LZXE3OC&LDB~S4Row10gJ+#E5bV~_^LM;1 z3p>g>Z`Ygi`e9HVzLq%@?23VlJ_)n9DIGu~9I`+=vvcH)?HqS}pv{5>$2;HGZ{MxJ z)>qqgX)H9Fu=RupM31SR$6$dBnGTt76{i5?NM{b_5$ZUj_}^Xjfa97Y@D&98jJ~Yj zV;*-ypEK4kjj6=>CA2+M+BgPq(lk3()eHLU>X@QwR0QMHg|^Pa2$9P)C@Bp}3JMF# z$e;#fgDKNBh*ceI3Qz45txdwHn3zX-&(stdrYGIyr-Ise_ zFXr%1K+Kr4u@*SW=IG{YFjN{@MA2tljsBX)MfU(sT%DuP!Jt1GG_SfCfi6~1eT+aK zD^iQ5RUez8HqBrkqge<_N!rScJsu8tgeC1`Bu5v{QJo6SNsW_<8n49(tMePMRFeLf zX{to@&9$$9b6gwyxepqvn`@WQw-Bc+%A8bXF0O91z8+In7*mG4OQ@?~tS)tbb$zS0 zzE~O480Up@X!V$V38jk{D>WBt^@rb%X&ko^c|L}0bm9CYgdYQ;+B8&7+@_&r$Kljf z<1n$e+J z45$+mPWlAYM$)lp%1Kkpv1!`LP}|AUjFY2TCr@)uf##heEx;LFbV_u}xk5`&OIM_Y zKDa;NEW!$p(5|SAfObW=r3ifz;f5nj-w17u9E2wb9fYR{zaY2>zan@5t)uA)SINXe z6XGq6^~R!1EUh;hEt!1WXf{{3q`A=CY{=B3+ESynQIlrO{T z*IZdz-cTak#?LaNNDzfY%u^zf%qw0j8tLrur<)C=3W_>b^?{8lk&0z0)j*yb#vj1G z?_q@KB#W4nw{iLn$MyeDco*OgB)SReBTUig8wAYL=sN^V)~JdwhkzzU6oDZ;N9ZDK zBLoO95Ox4sya9Nwcea4B1mnMY2iITh9c%&@CqE=|GCrx>;x+v4V8Ze|Ks18@Lu*x1 zlZlvISjSwF#^TB&nQX1DKdLolTBVs=Yc|id3eCq{SI6oo<{aN*^FWoOM@gn!mkxZ_ z<@o!~vDSG8A&r1PTO5CXIR1Rd4g7-fSHEfmu literal 0 HcmV?d00001 diff --git a/lib/colorama/__pycache__/ansitowin32.cpython-39.pyc b/lib/colorama/__pycache__/ansitowin32.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..382e5f1f8c9bbf4917f5016191cf8c50e2e491c3 GIT binary patch literal 8283 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�$pU*TUR{ z^sRR!(n=J28x=)dhM4<{|A`oQv7p#vg!>Z;eY8bVw{@nieJngJ-bGzeg@^)xzP(HB zdE4h_2@Hwpxf{}%-p$BFf+xZdo@dO%m>uV$4hHXw9kJoPP-EwV>D|~FkKHELoW<@I zW=4eU>_^)kV@J|yXl~Au)D#~o;1o@xL;jMPo6~4Z5M2I~WIE73N|#m3ZkCfAUlqhD z0~<7M4&wbw&Bs#8dcG(qp4 zXHRQDTM}PnS9*@?JWd~jI*(HZ@ILj-RPCd+JF>u+$aqf~dH{dUDZ9Ld9@OO9ol;G5 deV-iY!Aua!iI@wLV%A4(1GekN-N! zZ%i1*->K32^`Y?Sw)k7#-hY;+%gT+yv@LJ#xXjsN2J0 zY@cnJ;-VhEggP0|d6)HgwtfYpm($V9>F8B2uSZ8QmQTm>>DXu9lpY%b9T%fwm};Dn z5mV{RsdVNw@4B9eGd`MoJ)L_!oqNNZ#+)DFCe!K2bUJd=E9hO0&M`$Hol!_<-12Ux z=Zr(%+v(_Sf07UKAs{`&hxrKVeqmG@ALV1OnRkJo;b*lLruaBNkDjZ1f={C7bAEwe zM1754;+Ij+@+YF^zKSO7#`LY-W zTu5}dyR}yQaz{iJnsW3pFjO9)M6>}n%Ent(dJCeMFCBDg^6y_Qe7W%~5;EE-S7dEx zV^Kse;&5kUYbW-jZ8~Hl7Eye60|#$JwOD+<6I5OVTO!)1gpE)J&EN}&aPRKy&P(Oq zZv@Rv9y}Pq{{KKRBzeOB7fohpu9hm><#dyLMQ<`nOg~7$R+-J0QdCu6IhO58+?GN) z6~D3_NR@rMvRqnQT0Lcrb9xSFO}Okt$MPJ8VYYT8 z8r5Snl%!^r^?hLH`*}2?ArOO^Yydx-O|XIYdsC;QKASErK{>APw}PhdeUHn|9DhM{pDmE9lNFtgNgqO7an9uY*;&@{_q@(O+F!Si@G;nrQIImzOc2N8V%G zBvU_+5)tDv+jN-2T>PB(c2-j73AI`1pu@Ds{1E|B;9n!d4VE1bkJ9)_l8K6>_;%4JBEEvNyx9QLvP=9kh5uD7zAE)>JlX zS1KZkdZgEA5BM<8V5jDje>bo`(pd}Ao6J45O?exAANdhEs+Ro5RK&DTcEV#o_> z?X#-6Xy6u*zF?J3Tph{IpzB9e#z8v9Z-;PogRcD?@#-6s!c=l;rjIX}%ACEU*!G9Q z_ubzTE%Dusd_cSDxUhGn=RULd8(}4AL=R@W6A?G8p!HG>5{~7VKTbe$%4>@&&(;*x zORK9r2X%D}A3a7&lJqoWK*vLe@*cPo*Cg$BHJnES7X%`tV`$nWEhqYgrlIGf3)}Jz z=4w^TY2rwwkhu>oA%dn~(ab)$dQyu8>lWK2i=9 ztzYu!RB{v6Nw(RQ)zq@83BTO6$Tjg@+zn&}Oa27QRVF}aixRm%koNp3OJ8A~&R8U> z`~_NT6_V-bqiwqA(!I!heh&}ousY-H1&c0$Lu7x!WS-24#xY_}P;FA3;4XP=#r8g{ zXI|MI%cj6-*PWjmrr|T)Hqp-NwizQ>LW7yOuaoImgad3_5_In<3>R{83Y8?AmJ|yl zK`VbuguA?wjy&c7BG^qvmyF+FqKMC!`F|;Rl z{~ZpGNFZQqo@KT@j&PM@BW%P3*1tr5g`hq9C=*lB<}eQ?H3u8!zz)neiBVezcE^0f zC}4nU38dld)o_gNN&W<`wFCR_7KCTft4Nor1h_1hOO^8g#bGEb1V9$$waD zG}36d(vEl9@dEODP>F?{OQK!o+n~`Fg=~VDt<%5L5Us6vTVtmDyDsn|ZqyfMC~z~meWmeod{fIQrj&!3R} zQ-H=pR=?(zi)Y`Gl%bS#$G27jt&Qi5_@ogJ9Y=rK><27L^Et8<9#C-bXXraU-e15d zp-6H5m5B?C$J)ui$4mK0d`k`Y;HriGHI~U$B4r{UMERj(Y-#r2FeS~MImjf}(JrvH z!WT)byo;uqS}M)W<7NGPd3EjCT+zp4{>gF)%+uACg{5+Nx%5ctLrE3N0ZfaU- zd83iUkfE-tXarUNa&sq?aRMi$6rHp+-g1zd6o%xm-p^0ogF2p*<16=tkS)=;cUKMc zz8ez1(ca<)aw}1q^LVbbSX@$Bi6<5QDNFLeG;L$gudEd1&*-3QL@3%y3KQz$NB+dqFQF07Z=R<(-dQ(8mN%I8Gr8K=XQw&orUX6jy9u4x}SmTRJB&VL*`;p{GF9&=cSUEVv{ cbk50kcNe$2BHhJt?SZUo=f>O|ewLg4FF%gxumAu6 literal 0 HcmV?d00001 diff --git a/lib/colorama/__pycache__/winterm.cpython-39.pyc b/lib/colorama/__pycache__/winterm.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adbc09084a5ebfa20b24a31041db063f50451432 GIT binary patch literal 5241 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 A00000 literal 0 HcmV?d00001 diff --git a/lib/colorama/ansi.py b/lib/colorama/ansi.py new file mode 100644 index 0000000..11ec695 --- /dev/null +++ b/lib/colorama/ansi.py @@ -0,0 +1,102 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +''' +This module generates ANSI character codes to printing colors to terminals. +See: http://en.wikipedia.org/wiki/ANSI_escape_code +''' + +CSI = '\033[' +OSC = '\033]' +BEL = '\a' + + +def code_to_chars(code): + return CSI + str(code) + 'm' + +def set_title(title): + return OSC + '2;' + title + BEL + +def clear_screen(mode=2): + return CSI + str(mode) + 'J' + +def clear_line(mode=2): + return CSI + str(mode) + 'K' + + +class AnsiCodes(object): + def __init__(self): + # the subclasses declare class attributes which are numbers. + # Upon instantiation we define instance attributes, which are the same + # as the class attributes but wrapped with the ANSI escape sequence + for name in dir(self): + if not name.startswith('_'): + value = getattr(self, name) + setattr(self, name, code_to_chars(value)) + + +class AnsiCursor(object): + def UP(self, n=1): + return CSI + str(n) + 'A' + def DOWN(self, n=1): + return CSI + str(n) + 'B' + def FORWARD(self, n=1): + return CSI + str(n) + 'C' + def BACK(self, n=1): + return CSI + str(n) + 'D' + def POS(self, x=1, y=1): + return CSI + str(y) + ';' + str(x) + 'H' + + +class AnsiFore(AnsiCodes): + BLACK = 30 + RED = 31 + GREEN = 32 + YELLOW = 33 + BLUE = 34 + MAGENTA = 35 + CYAN = 36 + WHITE = 37 + RESET = 39 + + # These are fairly well supported, but not part of the standard. + LIGHTBLACK_EX = 90 + LIGHTRED_EX = 91 + LIGHTGREEN_EX = 92 + LIGHTYELLOW_EX = 93 + LIGHTBLUE_EX = 94 + LIGHTMAGENTA_EX = 95 + LIGHTCYAN_EX = 96 + LIGHTWHITE_EX = 97 + + +class AnsiBack(AnsiCodes): + BLACK = 40 + RED = 41 + GREEN = 42 + YELLOW = 43 + BLUE = 44 + MAGENTA = 45 + CYAN = 46 + WHITE = 47 + RESET = 49 + + # These are fairly well supported, but not part of the standard. + LIGHTBLACK_EX = 100 + LIGHTRED_EX = 101 + LIGHTGREEN_EX = 102 + LIGHTYELLOW_EX = 103 + LIGHTBLUE_EX = 104 + LIGHTMAGENTA_EX = 105 + LIGHTCYAN_EX = 106 + LIGHTWHITE_EX = 107 + + +class AnsiStyle(AnsiCodes): + BRIGHT = 1 + DIM = 2 + NORMAL = 22 + RESET_ALL = 0 + +Fore = AnsiFore() +Back = AnsiBack() +Style = AnsiStyle() +Cursor = AnsiCursor() diff --git a/lib/colorama/ansitowin32.py b/lib/colorama/ansitowin32.py new file mode 100644 index 0000000..abf209e --- /dev/null +++ b/lib/colorama/ansitowin32.py @@ -0,0 +1,277 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import re +import sys +import os + +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL +from .winterm import enable_vt_processing, WinTerm, WinColor, WinStyle +from .win32 import windll, winapi_test + + +winterm = None +if windll is not None: + winterm = WinTerm() + + +class StreamWrapper(object): + ''' + Wraps a stream (such as stdout), acting as a transparent proxy for all + attribute access apart from method 'write()', which is delegated to our + Converter instance. + ''' + def __init__(self, wrapped, converter): + # double-underscore everything to prevent clashes with names of + # attributes on the wrapped stream object. + self.__wrapped = wrapped + self.__convertor = converter + + def __getattr__(self, name): + return getattr(self.__wrapped, name) + + def __enter__(self, *args, **kwargs): + # special method lookup bypasses __getattr__/__getattribute__, see + # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit + # thus, contextlib magic methods are not proxied via __getattr__ + return self.__wrapped.__enter__(*args, **kwargs) + + def __exit__(self, *args, **kwargs): + return self.__wrapped.__exit__(*args, **kwargs) + + def __setstate__(self, state): + self.__dict__ = state + + def __getstate__(self): + return self.__dict__ + + def write(self, text): + self.__convertor.write(text) + + def isatty(self): + stream = self.__wrapped + if 'PYCHARM_HOSTED' in os.environ: + if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__): + return True + try: + stream_isatty = stream.isatty + except AttributeError: + return False + else: + return stream_isatty() + + @property + def closed(self): + stream = self.__wrapped + try: + return stream.closed + # AttributeError in the case that the stream doesn't support being closed + # ValueError for the case that the stream has already been detached when atexit runs + except (AttributeError, ValueError): + return True + + +class AnsiToWin32(object): + ''' + Implements a 'write()' method which, on Windows, will strip ANSI character + sequences from the text, and if outputting to a tty, will convert them into + win32 function calls. + ''' + ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer + ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command + + def __init__(self, wrapped, convert=None, strip=None, autoreset=False): + # The wrapped stream (normally sys.stdout or sys.stderr) + self.wrapped = wrapped + + # should we reset colors to defaults after every .write() + self.autoreset = autoreset + + # create the proxy wrapping our output stream + self.stream = StreamWrapper(wrapped, self) + + on_windows = os.name == 'nt' + # We test if the WinAPI works, because even if we are on Windows + # we may be using a terminal that doesn't support the WinAPI + # (e.g. Cygwin Terminal). In this case it's up to the terminal + # to support the ANSI codes. + conversion_supported = on_windows and winapi_test() + try: + fd = wrapped.fileno() + except Exception: + fd = -1 + system_has_native_ansi = not on_windows or enable_vt_processing(fd) + have_tty = not self.stream.closed and self.stream.isatty() + need_conversion = conversion_supported and not system_has_native_ansi + + # should we strip ANSI sequences from our output? + if strip is None: + strip = need_conversion or not have_tty + self.strip = strip + + # should we should convert ANSI sequences into win32 calls? + if convert is None: + convert = need_conversion and have_tty + self.convert = convert + + # dict of ansi codes to win32 functions and parameters + self.win32_calls = self.get_win32_calls() + + # are we wrapping stderr? + self.on_stderr = self.wrapped is sys.stderr + + def should_wrap(self): + ''' + True if this class is actually needed. If false, then the output + stream will not be affected, nor will win32 calls be issued, so + wrapping stdout is not actually required. This will generally be + False on non-Windows platforms, unless optional functionality like + autoreset has been requested using kwargs to init() + ''' + return self.convert or self.strip or self.autoreset + + def get_win32_calls(self): + if self.convert and winterm: + return { + AnsiStyle.RESET_ALL: (winterm.reset_all, ), + AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), + AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), + AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), + AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), + AnsiFore.RED: (winterm.fore, WinColor.RED), + AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), + AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), + AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), + AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), + AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), + AnsiFore.WHITE: (winterm.fore, WinColor.GREY), + AnsiFore.RESET: (winterm.fore, ), + AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True), + AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True), + AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True), + AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True), + AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True), + AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True), + AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True), + AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True), + AnsiBack.BLACK: (winterm.back, WinColor.BLACK), + AnsiBack.RED: (winterm.back, WinColor.RED), + AnsiBack.GREEN: (winterm.back, WinColor.GREEN), + AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), + AnsiBack.BLUE: (winterm.back, WinColor.BLUE), + AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), + AnsiBack.CYAN: (winterm.back, WinColor.CYAN), + AnsiBack.WHITE: (winterm.back, WinColor.GREY), + AnsiBack.RESET: (winterm.back, ), + AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True), + AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True), + AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True), + AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True), + AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True), + AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True), + AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True), + AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True), + } + return dict() + + def write(self, text): + if self.strip or self.convert: + self.write_and_convert(text) + else: + self.wrapped.write(text) + self.wrapped.flush() + if self.autoreset: + self.reset_all() + + + def reset_all(self): + if self.convert: + self.call_win32('m', (0,)) + elif not self.strip and not self.stream.closed: + self.wrapped.write(Style.RESET_ALL) + + + def write_and_convert(self, text): + ''' + Write the given text to our wrapped stream, stripping any ANSI + sequences from the text, and optionally converting them into win32 + calls. + ''' + cursor = 0 + text = self.convert_osc(text) + for match in self.ANSI_CSI_RE.finditer(text): + start, end = match.span() + self.write_plain_text(text, cursor, start) + self.convert_ansi(*match.groups()) + cursor = end + self.write_plain_text(text, cursor, len(text)) + + + def write_plain_text(self, text, start, end): + if start < end: + self.wrapped.write(text[start:end]) + self.wrapped.flush() + + + def convert_ansi(self, paramstring, command): + if self.convert: + params = self.extract_params(command, paramstring) + self.call_win32(command, params) + + + def extract_params(self, command, paramstring): + if command in 'Hf': + params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';')) + while len(params) < 2: + # defaults: + params = params + (1,) + else: + params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0) + if len(params) == 0: + # defaults: + if command in 'JKm': + params = (0,) + elif command in 'ABCD': + params = (1,) + + return params + + + def call_win32(self, command, params): + if command == 'm': + for param in params: + if param in self.win32_calls: + func_args = self.win32_calls[param] + func = func_args[0] + args = func_args[1:] + kwargs = dict(on_stderr=self.on_stderr) + func(*args, **kwargs) + elif command in 'J': + winterm.erase_screen(params[0], on_stderr=self.on_stderr) + elif command in 'K': + winterm.erase_line(params[0], on_stderr=self.on_stderr) + elif command in 'Hf': # cursor position - absolute + winterm.set_cursor_position(params, on_stderr=self.on_stderr) + elif command in 'ABCD': # cursor position - relative + n = params[0] + # A - up, B - down, C - forward, D - back + x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command] + winterm.cursor_adjust(x, y, on_stderr=self.on_stderr) + + + def convert_osc(self, text): + for match in self.ANSI_OSC_RE.finditer(text): + start, end = match.span() + text = text[:start] + text[end:] + paramstring, command = match.groups() + if command == BEL: + if paramstring.count(";") == 1: + params = paramstring.split(";") + # 0 - change title and icon (we will only change title) + # 1 - change icon (we don't support this) + # 2 - change title + if params[0] in '02': + winterm.set_title(params[1]) + return text + + + def flush(self): + self.wrapped.flush() diff --git a/lib/colorama/initialise.py b/lib/colorama/initialise.py new file mode 100644 index 0000000..d5fd4b7 --- /dev/null +++ b/lib/colorama/initialise.py @@ -0,0 +1,121 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import atexit +import contextlib +import sys + +from .ansitowin32 import AnsiToWin32 + + +def _wipe_internal_state_for_tests(): + global orig_stdout, orig_stderr + orig_stdout = None + orig_stderr = None + + global wrapped_stdout, wrapped_stderr + wrapped_stdout = None + wrapped_stderr = None + + global atexit_done + atexit_done = False + + global fixed_windows_console + fixed_windows_console = False + + try: + # no-op if it wasn't registered + atexit.unregister(reset_all) + except AttributeError: + # python 2: no atexit.unregister. Oh well, we did our best. + pass + + +def reset_all(): + if AnsiToWin32 is not None: # Issue #74: objects might become None at exit + AnsiToWin32(orig_stdout).reset_all() + + +def init(autoreset=False, convert=None, strip=None, wrap=True): + + if not wrap and any([autoreset, convert, strip]): + raise ValueError('wrap=False conflicts with any other arg=True') + + global wrapped_stdout, wrapped_stderr + global orig_stdout, orig_stderr + + orig_stdout = sys.stdout + orig_stderr = sys.stderr + + if sys.stdout is None: + wrapped_stdout = None + else: + sys.stdout = wrapped_stdout = \ + wrap_stream(orig_stdout, convert, strip, autoreset, wrap) + if sys.stderr is None: + wrapped_stderr = None + else: + sys.stderr = wrapped_stderr = \ + wrap_stream(orig_stderr, convert, strip, autoreset, wrap) + + global atexit_done + if not atexit_done: + atexit.register(reset_all) + atexit_done = True + + +def deinit(): + if orig_stdout is not None: + sys.stdout = orig_stdout + if orig_stderr is not None: + sys.stderr = orig_stderr + + +def just_fix_windows_console(): + global fixed_windows_console + + if sys.platform != "win32": + return + if fixed_windows_console: + return + if wrapped_stdout is not None or wrapped_stderr is not None: + # Someone already ran init() and it did stuff, so we won't second-guess them + return + + # On newer versions of Windows, AnsiToWin32.__init__ will implicitly enable the + # native ANSI support in the console as a side-effect. We only need to actually + # replace sys.stdout/stderr if we're in the old-style conversion mode. + new_stdout = AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=False) + if new_stdout.convert: + sys.stdout = new_stdout + new_stderr = AnsiToWin32(sys.stderr, convert=None, strip=None, autoreset=False) + if new_stderr.convert: + sys.stderr = new_stderr + + fixed_windows_console = True + +@contextlib.contextmanager +def colorama_text(*args, **kwargs): + init(*args, **kwargs) + try: + yield + finally: + deinit() + + +def reinit(): + if wrapped_stdout is not None: + sys.stdout = wrapped_stdout + if wrapped_stderr is not None: + sys.stderr = wrapped_stderr + + +def wrap_stream(stream, convert, strip, autoreset, wrap): + if wrap: + wrapper = AnsiToWin32(stream, + convert=convert, strip=strip, autoreset=autoreset) + if wrapper.should_wrap(): + stream = wrapper.stream + return stream + + +# Use this for initial setup as well, to reduce code duplication +_wipe_internal_state_for_tests() diff --git a/lib/colorama/tests/__init__.py b/lib/colorama/tests/__init__.py new file mode 100644 index 0000000..8c5661e --- /dev/null +++ b/lib/colorama/tests/__init__.py @@ -0,0 +1 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. diff --git a/lib/colorama/tests/__pycache__/__init__.cpython-39.pyc b/lib/colorama/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d1bc1000ca68f9b7bc7f7af7e00adc248eac081 GIT binary patch literal 175 zcmYe~<>g`k0*|$lDc(T(F^Gc<7=auIATDMB5-AM944RC7D;bJF!U*D5h_h8pXmM&$ zaZGS>QD#AmOKNd;Nq#|0dO=BiaYkZFYEeu{YH^8Cj8A4#OmSvOs%}AIa&}^RYH>_* reolT-Vs4^7NI|iFe0*kJW=VX!UP0w84x8Nkl+v73JCJRkftUdRitjD% literal 0 HcmV?d00001 diff --git a/lib/colorama/tests/__pycache__/ansi_test.cpython-39.pyc b/lib/colorama/tests/__pycache__/ansi_test.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4049f2f44f49789651cb4b3464191a377ee7ca53 GIT binary patch literal 2535 zcmeHJTTdHD6rS0e*H;Xrq$z3Bd%Kki0o+Eesx}6;fNC5GjA#~1tA*K68?Wuo4vMh) zMt(^9$V30oy!I)tee6@unF;Z7iB$EuYwdUTo7r<_&dfJ^9F@xj0@vmr_t@PcA%7yt zUKWtYaHr1z7-6(e0<=b-CPrW+W?)jnOlI}1#18Dl37jMsDq@FaHw8A}!C5ZaYrJT9n4pN}}%Ir2*}p zDXrD$=tNpS40$Z=U2)crJt~WhLE3E(_q&6YO7{ zZqP}Kec8guF1esB&j~6~n#NqThQj~lH0n!BoQ+~>re~>iQo)9&g1Zn@k(l!fDy=l` zKf54&8k+y!Tl=Q72LaN~?h)^fI_q(IB8H>R@koT}&k>7xN5G7iJI(H4C+&*(%MmPH zbR4IhqhWu@qa<3y7t+NDRxiZIh0&R`(^%|{Dwq=HM$8#Hu3Vd^7@>xo_V-zGntWL7Be%`m`o64|hR?V;(HjNBd-ke+N;gR+KN2J z_YpooKuz$E5I#ou1mRPJ&k(S1yjfyc?j{mjgm?d&#IDA9v&0r)gn$2)R9_gYHMZst z^nNUZ0kV{MwqCLv#FOUw*0yw7PjW}sBcWB9QsAE>Ycj-SaVmMv^ literal 0 HcmV?d00001 diff --git a/lib/colorama/tests/__pycache__/ansitowin32_test.cpython-39.pyc b/lib/colorama/tests/__pycache__/ansitowin32_test.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..059e24566bcf69c02eb554bf237db94d0fb2a97f GIT binary patch literal 11538 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 literal 0 HcmV?d00001 diff --git a/lib/colorama/tests/__pycache__/winterm_test.cpython-39.pyc b/lib/colorama/tests/__pycache__/winterm_test.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da6fcd73516a681e6124051d5b7359dbcd449cb9 GIT binary patch literal 3310 zcmcImNpBlB6ec;N(To;bab#y{fd*+0RhOhqx}Zf`c!`@BaGb(ckYpfWFcE1-@{FXA zqq;VnlY<@#^wu9>AARVpzlPVI^cQ+a`yOW`JC>c~k`ed~Iq#9;``$u^<#K_*bN8=H z{P_eS|KQ;GPDrevoIU#%z?JH8X={X8WPN$09f zguA^;3I2@SJCZ*p9rgv`HqS+cxab$-l3$9;ewmUxggZR{g7CaxdzL>TCb_%GM+N}$fEQCsEI}TeNT~M2?-h)mgl&Udp-|IFvb*>jC&8GtfQ*g$^ zR%f9bb*1U8CEJnEdH7ZrGH&9s#~&L8v(VJv0D}0G6Q2o-(>=-=w_cDv>f0hGoOXWS z<@N#PxjmL{A0%F;>2l9#7REFSyl5oFF-eJ+jifXtnc$N~QqESd@N;H7k&UPLc{85m zRiJ8G7c=9Kshyc7njXgCwXIes0h954^F|ZN89rz?iHyy3gp#Xr?Ha&buHWmxRrM+@IK9>8*{0e{`< zHEs*FnRI)Njb0L{ry&=z0b5LNG?rUW8mg6u&wKDk;f7F+A-&g8L0!*C4sdj>x2;|5 zEOlDRB>azplp};W)S)i43Ha?)VPhDHb(}C%))uU@Q<36Xza@pb0y@0ArJ5AyXAe3M z88uG(6FSCe6r4tb6w!e+K$qFObU$|hsf3P6Aa+sb=sSMNBrZtUs}hj9oK?vMVsyNA z;nh`f_K{8k@>J6VfVCoV(}M~?t!P{)tI(Iz2r~%h0kpRvk{|@fD2WlGoyUvI%l9AZ z{Jpt5i!1dx>6x$dt4nw8)(@Gyh%+xCpaWBQ4^>LYA%qe-S)qcu3IL=V+8tV9E}epA zqXS;rcG|1H+_`wt5yM5!PL&;4`0P|c$*vXTI0J-)0n8h8cddQz07@J1TrkNrOdlNO z)AY$?F3%zB*(ld8atFtT+x*I)20Ix12hRXmmY8M|BduO5nG}=AyF7<5g^;F>0d!^dbx=^KFp6&=86(|cF2hKNSRbQ!IC(6v zN7ZN+7EA*>p+F}DXBO0S--a4Qvl>L(*0G8N^$IGKAr*-_w6h?@qEoNs(*7~a=F9=A zJ;$)n2y)r#u|Y@-_6yuVEKQ+{tfn_$`28XmNG*Y=&lD zW$1oq&HR~!$zx2FhD=>Oo2lUfE47MtgP;?}A_#OL2;wg9N7yd~0p4X&i9{mh`v_PH zwWYR|c6(8nK$(i+(wl@bQCqF#sr(3)K1IM1EWbqf3gJ71TL?#G%=GY_ltq9tG<5~Q zg)&y~Dzq>S?elX@=$OM?_23f zgcj-IQrzpptxd|V^lcRCJcKyj#mu>Hj`_4c;|Apq2&ZgcySGx}e#C!Pc(%E;${4NE M5}jdHtI~Jr|D`0@ga7~l literal 0 HcmV?d00001 diff --git a/lib/colorama/tests/ansi_test.py b/lib/colorama/tests/ansi_test.py new file mode 100644 index 0000000..0a20c80 --- /dev/null +++ b/lib/colorama/tests/ansi_test.py @@ -0,0 +1,76 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import sys +from unittest import TestCase, main + +from ..ansi import Back, Fore, Style +from ..ansitowin32 import AnsiToWin32 + +stdout_orig = sys.stdout +stderr_orig = sys.stderr + + +class AnsiTest(TestCase): + + def setUp(self): + # sanity check: stdout should be a file or StringIO object. + # It will only be AnsiToWin32 if init() has previously wrapped it + self.assertNotEqual(type(sys.stdout), AnsiToWin32) + self.assertNotEqual(type(sys.stderr), AnsiToWin32) + + def tearDown(self): + sys.stdout = stdout_orig + sys.stderr = stderr_orig + + + def testForeAttributes(self): + self.assertEqual(Fore.BLACK, '\033[30m') + self.assertEqual(Fore.RED, '\033[31m') + self.assertEqual(Fore.GREEN, '\033[32m') + self.assertEqual(Fore.YELLOW, '\033[33m') + self.assertEqual(Fore.BLUE, '\033[34m') + self.assertEqual(Fore.MAGENTA, '\033[35m') + self.assertEqual(Fore.CYAN, '\033[36m') + self.assertEqual(Fore.WHITE, '\033[37m') + self.assertEqual(Fore.RESET, '\033[39m') + + # Check the light, extended versions. + self.assertEqual(Fore.LIGHTBLACK_EX, '\033[90m') + self.assertEqual(Fore.LIGHTRED_EX, '\033[91m') + self.assertEqual(Fore.LIGHTGREEN_EX, '\033[92m') + self.assertEqual(Fore.LIGHTYELLOW_EX, '\033[93m') + self.assertEqual(Fore.LIGHTBLUE_EX, '\033[94m') + self.assertEqual(Fore.LIGHTMAGENTA_EX, '\033[95m') + self.assertEqual(Fore.LIGHTCYAN_EX, '\033[96m') + self.assertEqual(Fore.LIGHTWHITE_EX, '\033[97m') + + + def testBackAttributes(self): + self.assertEqual(Back.BLACK, '\033[40m') + self.assertEqual(Back.RED, '\033[41m') + self.assertEqual(Back.GREEN, '\033[42m') + self.assertEqual(Back.YELLOW, '\033[43m') + self.assertEqual(Back.BLUE, '\033[44m') + self.assertEqual(Back.MAGENTA, '\033[45m') + self.assertEqual(Back.CYAN, '\033[46m') + self.assertEqual(Back.WHITE, '\033[47m') + self.assertEqual(Back.RESET, '\033[49m') + + # Check the light, extended versions. + self.assertEqual(Back.LIGHTBLACK_EX, '\033[100m') + self.assertEqual(Back.LIGHTRED_EX, '\033[101m') + self.assertEqual(Back.LIGHTGREEN_EX, '\033[102m') + self.assertEqual(Back.LIGHTYELLOW_EX, '\033[103m') + self.assertEqual(Back.LIGHTBLUE_EX, '\033[104m') + self.assertEqual(Back.LIGHTMAGENTA_EX, '\033[105m') + self.assertEqual(Back.LIGHTCYAN_EX, '\033[106m') + self.assertEqual(Back.LIGHTWHITE_EX, '\033[107m') + + + def testStyleAttributes(self): + self.assertEqual(Style.DIM, '\033[2m') + self.assertEqual(Style.NORMAL, '\033[22m') + self.assertEqual(Style.BRIGHT, '\033[1m') + + +if __name__ == '__main__': + main() diff --git a/lib/colorama/tests/ansitowin32_test.py b/lib/colorama/tests/ansitowin32_test.py new file mode 100644 index 0000000..91ca551 --- /dev/null +++ b/lib/colorama/tests/ansitowin32_test.py @@ -0,0 +1,294 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from io import StringIO, TextIOWrapper +from unittest import TestCase, main +try: + from contextlib import ExitStack +except ImportError: + # python 2 + from contextlib2 import ExitStack + +try: + from unittest.mock import MagicMock, Mock, patch +except ImportError: + from mock import MagicMock, Mock, patch + +from ..ansitowin32 import AnsiToWin32, StreamWrapper +from ..win32 import ENABLE_VIRTUAL_TERMINAL_PROCESSING +from .utils import osname + + +class StreamWrapperTest(TestCase): + + def testIsAProxy(self): + mockStream = Mock() + wrapper = StreamWrapper(mockStream, None) + self.assertTrue( wrapper.random_attr is mockStream.random_attr ) + + def testDelegatesWrite(self): + mockStream = Mock() + mockConverter = Mock() + wrapper = StreamWrapper(mockStream, mockConverter) + wrapper.write('hello') + self.assertTrue(mockConverter.write.call_args, (('hello',), {})) + + def testDelegatesContext(self): + mockConverter = Mock() + s = StringIO() + with StreamWrapper(s, mockConverter) as fp: + fp.write(u'hello') + self.assertTrue(s.closed) + + def testProxyNoContextManager(self): + mockStream = MagicMock() + mockStream.__enter__.side_effect = AttributeError() + mockConverter = Mock() + with self.assertRaises(AttributeError) as excinfo: + with StreamWrapper(mockStream, mockConverter) as wrapper: + wrapper.write('hello') + + def test_closed_shouldnt_raise_on_closed_stream(self): + stream = StringIO() + stream.close() + wrapper = StreamWrapper(stream, None) + self.assertEqual(wrapper.closed, True) + + def test_closed_shouldnt_raise_on_detached_stream(self): + stream = TextIOWrapper(StringIO()) + stream.detach() + wrapper = StreamWrapper(stream, None) + self.assertEqual(wrapper.closed, True) + +class AnsiToWin32Test(TestCase): + + def testInit(self): + mockStdout = Mock() + auto = Mock() + stream = AnsiToWin32(mockStdout, autoreset=auto) + self.assertEqual(stream.wrapped, mockStdout) + self.assertEqual(stream.autoreset, auto) + + @patch('colorama.ansitowin32.winterm', None) + @patch('colorama.ansitowin32.winapi_test', lambda *_: True) + def testStripIsTrueOnWindows(self): + with osname('nt'): + mockStdout = Mock() + stream = AnsiToWin32(mockStdout) + self.assertTrue(stream.strip) + + def testStripIsFalseOffWindows(self): + with osname('posix'): + mockStdout = Mock(closed=False) + stream = AnsiToWin32(mockStdout) + self.assertFalse(stream.strip) + + def testWriteStripsAnsi(self): + mockStdout = Mock() + stream = AnsiToWin32(mockStdout) + stream.wrapped = Mock() + stream.write_and_convert = Mock() + stream.strip = True + + stream.write('abc') + + self.assertFalse(stream.wrapped.write.called) + self.assertEqual(stream.write_and_convert.call_args, (('abc',), {})) + + def testWriteDoesNotStripAnsi(self): + mockStdout = Mock() + stream = AnsiToWin32(mockStdout) + stream.wrapped = Mock() + stream.write_and_convert = Mock() + stream.strip = False + stream.convert = False + + stream.write('abc') + + self.assertFalse(stream.write_and_convert.called) + self.assertEqual(stream.wrapped.write.call_args, (('abc',), {})) + + def assert_autoresets(self, convert, autoreset=True): + stream = AnsiToWin32(Mock()) + stream.convert = convert + stream.reset_all = Mock() + stream.autoreset = autoreset + stream.winterm = Mock() + + stream.write('abc') + + self.assertEqual(stream.reset_all.called, autoreset) + + def testWriteAutoresets(self): + self.assert_autoresets(convert=True) + self.assert_autoresets(convert=False) + self.assert_autoresets(convert=True, autoreset=False) + self.assert_autoresets(convert=False, autoreset=False) + + def testWriteAndConvertWritesPlainText(self): + stream = AnsiToWin32(Mock()) + stream.write_and_convert( 'abc' ) + self.assertEqual( stream.wrapped.write.call_args, (('abc',), {}) ) + + def testWriteAndConvertStripsAllValidAnsi(self): + stream = AnsiToWin32(Mock()) + stream.call_win32 = Mock() + data = [ + 'abc\033[mdef', + 'abc\033[0mdef', + 'abc\033[2mdef', + 'abc\033[02mdef', + 'abc\033[002mdef', + 'abc\033[40mdef', + 'abc\033[040mdef', + 'abc\033[0;1mdef', + 'abc\033[40;50mdef', + 'abc\033[50;30;40mdef', + 'abc\033[Adef', + 'abc\033[0Gdef', + 'abc\033[1;20;128Hdef', + ] + for datum in data: + stream.wrapped.write.reset_mock() + stream.write_and_convert( datum ) + self.assertEqual( + [args[0] for args in stream.wrapped.write.call_args_list], + [ ('abc',), ('def',) ] + ) + + def testWriteAndConvertSkipsEmptySnippets(self): + stream = AnsiToWin32(Mock()) + stream.call_win32 = Mock() + stream.write_and_convert( '\033[40m\033[41m' ) + self.assertFalse( stream.wrapped.write.called ) + + def testWriteAndConvertCallsWin32WithParamsAndCommand(self): + stream = AnsiToWin32(Mock()) + stream.convert = True + stream.call_win32 = Mock() + stream.extract_params = Mock(return_value='params') + data = { + 'abc\033[adef': ('a', 'params'), + 'abc\033[;;bdef': ('b', 'params'), + 'abc\033[0cdef': ('c', 'params'), + 'abc\033[;;0;;Gdef': ('G', 'params'), + 'abc\033[1;20;128Hdef': ('H', 'params'), + } + for datum, expected in data.items(): + stream.call_win32.reset_mock() + stream.write_and_convert( datum ) + self.assertEqual( stream.call_win32.call_args[0], expected ) + + def test_reset_all_shouldnt_raise_on_closed_orig_stdout(self): + stream = StringIO() + converter = AnsiToWin32(stream) + stream.close() + + converter.reset_all() + + def test_wrap_shouldnt_raise_on_closed_orig_stdout(self): + stream = StringIO() + stream.close() + with \ + patch("colorama.ansitowin32.os.name", "nt"), \ + patch("colorama.ansitowin32.winapi_test", lambda: True): + converter = AnsiToWin32(stream) + self.assertTrue(converter.strip) + self.assertFalse(converter.convert) + + def test_wrap_shouldnt_raise_on_missing_closed_attr(self): + with \ + patch("colorama.ansitowin32.os.name", "nt"), \ + patch("colorama.ansitowin32.winapi_test", lambda: True): + converter = AnsiToWin32(object()) + self.assertTrue(converter.strip) + self.assertFalse(converter.convert) + + def testExtractParams(self): + stream = AnsiToWin32(Mock()) + data = { + '': (0,), + ';;': (0,), + '2': (2,), + ';;002;;': (2,), + '0;1': (0, 1), + ';;003;;456;;': (3, 456), + '11;22;33;44;55': (11, 22, 33, 44, 55), + } + for datum, expected in data.items(): + self.assertEqual(stream.extract_params('m', datum), expected) + + def testCallWin32UsesLookup(self): + listener = Mock() + stream = AnsiToWin32(listener) + stream.win32_calls = { + 1: (lambda *_, **__: listener(11),), + 2: (lambda *_, **__: listener(22),), + 3: (lambda *_, **__: listener(33),), + } + stream.call_win32('m', (3, 1, 99, 2)) + self.assertEqual( + [a[0][0] for a in listener.call_args_list], + [33, 11, 22] ) + + def test_osc_codes(self): + mockStdout = Mock() + stream = AnsiToWin32(mockStdout, convert=True) + with patch('colorama.ansitowin32.winterm') as winterm: + data = [ + '\033]0\x07', # missing arguments + '\033]0;foo\x08', # wrong OSC command + '\033]0;colorama_test_title\x07', # should work + '\033]1;colorama_test_title\x07', # wrong set command + '\033]2;colorama_test_title\x07', # should work + '\033]' + ';' * 64 + '\x08', # see issue #247 + ] + for code in data: + stream.write(code) + self.assertEqual(winterm.set_title.call_count, 2) + + def test_native_windows_ansi(self): + with ExitStack() as stack: + def p(a, b): + stack.enter_context(patch(a, b, create=True)) + # Pretend to be on Windows + p("colorama.ansitowin32.os.name", "nt") + p("colorama.ansitowin32.winapi_test", lambda: True) + p("colorama.win32.winapi_test", lambda: True) + p("colorama.winterm.win32.windll", "non-None") + p("colorama.winterm.get_osfhandle", lambda _: 1234) + + # Pretend that our mock stream has native ANSI support + p( + "colorama.winterm.win32.GetConsoleMode", + lambda _: ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ) + SetConsoleMode = Mock() + p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode) + + stdout = Mock() + stdout.closed = False + stdout.isatty.return_value = True + stdout.fileno.return_value = 1 + + # Our fake console says it has native vt support, so AnsiToWin32 should + # enable that support and do nothing else. + stream = AnsiToWin32(stdout) + SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self.assertFalse(stream.strip) + self.assertFalse(stream.convert) + self.assertFalse(stream.should_wrap()) + + # Now let's pretend we're on an old Windows console, that doesn't have + # native ANSI support. + p("colorama.winterm.win32.GetConsoleMode", lambda _: 0) + SetConsoleMode = Mock() + p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode) + + stream = AnsiToWin32(stdout) + SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self.assertTrue(stream.strip) + self.assertTrue(stream.convert) + self.assertTrue(stream.should_wrap()) + + +if __name__ == '__main__': + main() diff --git a/lib/colorama/tests/initialise_test.py b/lib/colorama/tests/initialise_test.py new file mode 100644 index 0000000..89f9b07 --- /dev/null +++ b/lib/colorama/tests/initialise_test.py @@ -0,0 +1,189 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import sys +from unittest import TestCase, main, skipUnless + +try: + from unittest.mock import patch, Mock +except ImportError: + from mock import patch, Mock + +from ..ansitowin32 import StreamWrapper +from ..initialise import init, just_fix_windows_console, _wipe_internal_state_for_tests +from .utils import osname, replace_by + +orig_stdout = sys.stdout +orig_stderr = sys.stderr + + +class InitTest(TestCase): + + @skipUnless(sys.stdout.isatty(), "sys.stdout is not a tty") + def setUp(self): + # sanity check + self.assertNotWrapped() + + def tearDown(self): + _wipe_internal_state_for_tests() + sys.stdout = orig_stdout + sys.stderr = orig_stderr + + def assertWrapped(self): + self.assertIsNot(sys.stdout, orig_stdout, 'stdout should be wrapped') + self.assertIsNot(sys.stderr, orig_stderr, 'stderr should be wrapped') + self.assertTrue(isinstance(sys.stdout, StreamWrapper), + 'bad stdout wrapper') + self.assertTrue(isinstance(sys.stderr, StreamWrapper), + 'bad stderr wrapper') + + def assertNotWrapped(self): + self.assertIs(sys.stdout, orig_stdout, 'stdout should not be wrapped') + self.assertIs(sys.stderr, orig_stderr, 'stderr should not be wrapped') + + @patch('colorama.initialise.reset_all') + @patch('colorama.ansitowin32.winapi_test', lambda *_: True) + @patch('colorama.ansitowin32.enable_vt_processing', lambda *_: False) + def testInitWrapsOnWindows(self, _): + with osname("nt"): + init() + self.assertWrapped() + + @patch('colorama.initialise.reset_all') + @patch('colorama.ansitowin32.winapi_test', lambda *_: False) + def testInitDoesntWrapOnEmulatedWindows(self, _): + with osname("nt"): + init() + self.assertNotWrapped() + + def testInitDoesntWrapOnNonWindows(self): + with osname("posix"): + init() + self.assertNotWrapped() + + def testInitDoesntWrapIfNone(self): + with replace_by(None): + init() + # We can't use assertNotWrapped here because replace_by(None) + # changes stdout/stderr already. + self.assertIsNone(sys.stdout) + self.assertIsNone(sys.stderr) + + def testInitAutoresetOnWrapsOnAllPlatforms(self): + with osname("posix"): + init(autoreset=True) + self.assertWrapped() + + def testInitWrapOffDoesntWrapOnWindows(self): + with osname("nt"): + init(wrap=False) + self.assertNotWrapped() + + def testInitWrapOffIncompatibleWithAutoresetOn(self): + self.assertRaises(ValueError, lambda: init(autoreset=True, wrap=False)) + + @patch('colorama.win32.SetConsoleTextAttribute') + @patch('colorama.initialise.AnsiToWin32') + def testAutoResetPassedOn(self, mockATW32, _): + with osname("nt"): + init(autoreset=True) + self.assertEqual(len(mockATW32.call_args_list), 2) + self.assertEqual(mockATW32.call_args_list[1][1]['autoreset'], True) + self.assertEqual(mockATW32.call_args_list[0][1]['autoreset'], True) + + @patch('colorama.initialise.AnsiToWin32') + def testAutoResetChangeable(self, mockATW32): + with osname("nt"): + init() + + init(autoreset=True) + self.assertEqual(len(mockATW32.call_args_list), 4) + self.assertEqual(mockATW32.call_args_list[2][1]['autoreset'], True) + self.assertEqual(mockATW32.call_args_list[3][1]['autoreset'], True) + + init() + self.assertEqual(len(mockATW32.call_args_list), 6) + self.assertEqual( + mockATW32.call_args_list[4][1]['autoreset'], False) + self.assertEqual( + mockATW32.call_args_list[5][1]['autoreset'], False) + + + @patch('colorama.initialise.atexit.register') + def testAtexitRegisteredOnlyOnce(self, mockRegister): + init() + self.assertTrue(mockRegister.called) + mockRegister.reset_mock() + init() + self.assertFalse(mockRegister.called) + + +class JustFixWindowsConsoleTest(TestCase): + def _reset(self): + _wipe_internal_state_for_tests() + sys.stdout = orig_stdout + sys.stderr = orig_stderr + + def tearDown(self): + self._reset() + + @patch("colorama.ansitowin32.winapi_test", lambda: True) + def testJustFixWindowsConsole(self): + if sys.platform != "win32": + # just_fix_windows_console should be a no-op + just_fix_windows_console() + self.assertIs(sys.stdout, orig_stdout) + self.assertIs(sys.stderr, orig_stderr) + else: + def fake_std(): + # Emulate stdout=not a tty, stderr=tty + # to check that we handle both cases correctly + stdout = Mock() + stdout.closed = False + stdout.isatty.return_value = False + stdout.fileno.return_value = 1 + sys.stdout = stdout + + stderr = Mock() + stderr.closed = False + stderr.isatty.return_value = True + stderr.fileno.return_value = 2 + sys.stderr = stderr + + for native_ansi in [False, True]: + with patch( + 'colorama.ansitowin32.enable_vt_processing', + lambda *_: native_ansi + ): + self._reset() + fake_std() + + # Regular single-call test + prev_stdout = sys.stdout + prev_stderr = sys.stderr + just_fix_windows_console() + self.assertIs(sys.stdout, prev_stdout) + if native_ansi: + self.assertIs(sys.stderr, prev_stderr) + else: + self.assertIsNot(sys.stderr, prev_stderr) + + # second call without resetting is always a no-op + prev_stdout = sys.stdout + prev_stderr = sys.stderr + just_fix_windows_console() + self.assertIs(sys.stdout, prev_stdout) + self.assertIs(sys.stderr, prev_stderr) + + self._reset() + fake_std() + + # If init() runs first, just_fix_windows_console should be a no-op + init() + prev_stdout = sys.stdout + prev_stderr = sys.stderr + just_fix_windows_console() + self.assertIs(prev_stdout, sys.stdout) + self.assertIs(prev_stderr, sys.stderr) + + +if __name__ == '__main__': + main() diff --git a/lib/colorama/tests/isatty_test.py b/lib/colorama/tests/isatty_test.py new file mode 100644 index 0000000..0f84e4b --- /dev/null +++ b/lib/colorama/tests/isatty_test.py @@ -0,0 +1,57 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import sys +from unittest import TestCase, main + +from ..ansitowin32 import StreamWrapper, AnsiToWin32 +from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY + + +def is_a_tty(stream): + return StreamWrapper(stream, None).isatty() + +class IsattyTest(TestCase): + + def test_TTY(self): + tty = StreamTTY() + self.assertTrue(is_a_tty(tty)) + with pycharm(): + self.assertTrue(is_a_tty(tty)) + + def test_nonTTY(self): + non_tty = StreamNonTTY() + self.assertFalse(is_a_tty(non_tty)) + with pycharm(): + self.assertFalse(is_a_tty(non_tty)) + + def test_withPycharm(self): + with pycharm(): + self.assertTrue(is_a_tty(sys.stderr)) + self.assertTrue(is_a_tty(sys.stdout)) + + def test_withPycharmTTYOverride(self): + tty = StreamTTY() + with pycharm(), replace_by(tty): + self.assertTrue(is_a_tty(tty)) + + def test_withPycharmNonTTYOverride(self): + non_tty = StreamNonTTY() + with pycharm(), replace_by(non_tty): + self.assertFalse(is_a_tty(non_tty)) + + def test_withPycharmNoneOverride(self): + with pycharm(): + with replace_by(None), replace_original_by(None): + self.assertFalse(is_a_tty(None)) + self.assertFalse(is_a_tty(StreamNonTTY())) + self.assertTrue(is_a_tty(StreamTTY())) + + def test_withPycharmStreamWrapped(self): + with pycharm(): + self.assertTrue(AnsiToWin32(StreamTTY()).stream.isatty()) + self.assertFalse(AnsiToWin32(StreamNonTTY()).stream.isatty()) + self.assertTrue(AnsiToWin32(sys.stdout).stream.isatty()) + self.assertTrue(AnsiToWin32(sys.stderr).stream.isatty()) + + +if __name__ == '__main__': + main() diff --git a/lib/colorama/tests/utils.py b/lib/colorama/tests/utils.py new file mode 100644 index 0000000..472fafb --- /dev/null +++ b/lib/colorama/tests/utils.py @@ -0,0 +1,49 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from contextlib import contextmanager +from io import StringIO +import sys +import os + + +class StreamTTY(StringIO): + def isatty(self): + return True + +class StreamNonTTY(StringIO): + def isatty(self): + return False + +@contextmanager +def osname(name): + orig = os.name + os.name = name + yield + os.name = orig + +@contextmanager +def replace_by(stream): + orig_stdout = sys.stdout + orig_stderr = sys.stderr + sys.stdout = stream + sys.stderr = stream + yield + sys.stdout = orig_stdout + sys.stderr = orig_stderr + +@contextmanager +def replace_original_by(stream): + orig_stdout = sys.__stdout__ + orig_stderr = sys.__stderr__ + sys.__stdout__ = stream + sys.__stderr__ = stream + yield + sys.__stdout__ = orig_stdout + sys.__stderr__ = orig_stderr + +@contextmanager +def pycharm(): + os.environ["PYCHARM_HOSTED"] = "1" + non_tty = StreamNonTTY() + with replace_by(non_tty), replace_original_by(non_tty): + yield + del os.environ["PYCHARM_HOSTED"] diff --git a/lib/colorama/tests/winterm_test.py b/lib/colorama/tests/winterm_test.py new file mode 100644 index 0000000..d0955f9 --- /dev/null +++ b/lib/colorama/tests/winterm_test.py @@ -0,0 +1,131 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import sys +from unittest import TestCase, main, skipUnless + +try: + from unittest.mock import Mock, patch +except ImportError: + from mock import Mock, patch + +from ..winterm import WinColor, WinStyle, WinTerm + + +class WinTermTest(TestCase): + + @patch('colorama.winterm.win32') + def testInit(self, mockWin32): + mockAttr = Mock() + mockAttr.wAttributes = 7 + 6 * 16 + 8 + mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr + term = WinTerm() + self.assertEqual(term._fore, 7) + self.assertEqual(term._back, 6) + self.assertEqual(term._style, 8) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + def testGetAttrs(self): + term = WinTerm() + + term._fore = 0 + term._back = 0 + term._style = 0 + self.assertEqual(term.get_attrs(), 0) + + term._fore = WinColor.YELLOW + self.assertEqual(term.get_attrs(), WinColor.YELLOW) + + term._back = WinColor.MAGENTA + self.assertEqual( + term.get_attrs(), + WinColor.YELLOW + WinColor.MAGENTA * 16) + + term._style = WinStyle.BRIGHT + self.assertEqual( + term.get_attrs(), + WinColor.YELLOW + WinColor.MAGENTA * 16 + WinStyle.BRIGHT) + + @patch('colorama.winterm.win32') + def testResetAll(self, mockWin32): + mockAttr = Mock() + mockAttr.wAttributes = 1 + 2 * 16 + 8 + mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr + term = WinTerm() + + term.set_console = Mock() + term._fore = -1 + term._back = -1 + term._style = -1 + + term.reset_all() + + self.assertEqual(term._fore, 1) + self.assertEqual(term._back, 2) + self.assertEqual(term._style, 8) + self.assertEqual(term.set_console.called, True) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + def testFore(self): + term = WinTerm() + term.set_console = Mock() + term._fore = 0 + + term.fore(5) + + self.assertEqual(term._fore, 5) + self.assertEqual(term.set_console.called, True) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + def testBack(self): + term = WinTerm() + term.set_console = Mock() + term._back = 0 + + term.back(5) + + self.assertEqual(term._back, 5) + self.assertEqual(term.set_console.called, True) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + def testStyle(self): + term = WinTerm() + term.set_console = Mock() + term._style = 0 + + term.style(22) + + self.assertEqual(term._style, 22) + self.assertEqual(term.set_console.called, True) + + @patch('colorama.winterm.win32') + def testSetConsole(self, mockWin32): + mockAttr = Mock() + mockAttr.wAttributes = 0 + mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr + term = WinTerm() + term.windll = Mock() + + term.set_console() + + self.assertEqual( + mockWin32.SetConsoleTextAttribute.call_args, + ((mockWin32.STDOUT, term.get_attrs()), {}) + ) + + @patch('colorama.winterm.win32') + def testSetConsoleOnStderr(self, mockWin32): + mockAttr = Mock() + mockAttr.wAttributes = 0 + mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr + term = WinTerm() + term.windll = Mock() + + term.set_console(on_stderr=True) + + self.assertEqual( + mockWin32.SetConsoleTextAttribute.call_args, + ((mockWin32.STDERR, term.get_attrs()), {}) + ) + + +if __name__ == '__main__': + main() diff --git a/lib/colorama/win32.py b/lib/colorama/win32.py new file mode 100644 index 0000000..841b0e2 --- /dev/null +++ b/lib/colorama/win32.py @@ -0,0 +1,180 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. + +# from winbase.h +STDOUT = -11 +STDERR = -12 + +ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + +try: + import ctypes + from ctypes import LibraryLoader + windll = LibraryLoader(ctypes.WinDLL) + from ctypes import wintypes +except (AttributeError, ImportError): + windll = None + SetConsoleTextAttribute = lambda *_: None + winapi_test = lambda *_: None +else: + from ctypes import byref, Structure, c_char, POINTER + + COORD = wintypes._COORD + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + def __str__(self): + return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( + self.dwSize.Y, self.dwSize.X + , self.dwCursorPosition.Y, self.dwCursorPosition.X + , self.wAttributes + , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right + , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X + ) + + _GetStdHandle = windll.kernel32.GetStdHandle + _GetStdHandle.argtypes = [ + wintypes.DWORD, + ] + _GetStdHandle.restype = wintypes.HANDLE + + _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo + _GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + POINTER(CONSOLE_SCREEN_BUFFER_INFO), + ] + _GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute + _SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + ] + _SetConsoleTextAttribute.restype = wintypes.BOOL + + _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition + _SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + COORD, + ] + _SetConsoleCursorPosition.restype = wintypes.BOOL + + _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA + _FillConsoleOutputCharacterA.argtypes = [ + wintypes.HANDLE, + c_char, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputCharacterA.restype = wintypes.BOOL + + _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute + _FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputAttribute.restype = wintypes.BOOL + + _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW + _SetConsoleTitleW.argtypes = [ + wintypes.LPCWSTR + ] + _SetConsoleTitleW.restype = wintypes.BOOL + + _GetConsoleMode = windll.kernel32.GetConsoleMode + _GetConsoleMode.argtypes = [ + wintypes.HANDLE, + POINTER(wintypes.DWORD) + ] + _GetConsoleMode.restype = wintypes.BOOL + + _SetConsoleMode = windll.kernel32.SetConsoleMode + _SetConsoleMode.argtypes = [ + wintypes.HANDLE, + wintypes.DWORD + ] + _SetConsoleMode.restype = wintypes.BOOL + + def _winapi_test(handle): + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return bool(success) + + def winapi_test(): + return any(_winapi_test(h) for h in + (_GetStdHandle(STDOUT), _GetStdHandle(STDERR))) + + def GetConsoleScreenBufferInfo(stream_id=STDOUT): + handle = _GetStdHandle(stream_id) + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return csbi + + def SetConsoleTextAttribute(stream_id, attrs): + handle = _GetStdHandle(stream_id) + return _SetConsoleTextAttribute(handle, attrs) + + def SetConsoleCursorPosition(stream_id, position, adjust=True): + position = COORD(*position) + # If the position is out of range, do nothing. + if position.Y <= 0 or position.X <= 0: + return + # Adjust for Windows' SetConsoleCursorPosition: + # 1. being 0-based, while ANSI is 1-based. + # 2. expecting (x,y), while ANSI uses (y,x). + adjusted_position = COORD(position.Y - 1, position.X - 1) + if adjust: + # Adjust for viewport's scroll position + sr = GetConsoleScreenBufferInfo(STDOUT).srWindow + adjusted_position.Y += sr.Top + adjusted_position.X += sr.Left + # Resume normal processing + handle = _GetStdHandle(stream_id) + return _SetConsoleCursorPosition(handle, adjusted_position) + + def FillConsoleOutputCharacter(stream_id, char, length, start): + handle = _GetStdHandle(stream_id) + char = c_char(char.encode()) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + success = _FillConsoleOutputCharacterA( + handle, char, length, start, byref(num_written)) + return num_written.value + + def FillConsoleOutputAttribute(stream_id, attr, length, start): + ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' + handle = _GetStdHandle(stream_id) + attribute = wintypes.WORD(attr) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + return _FillConsoleOutputAttribute( + handle, attribute, length, start, byref(num_written)) + + def SetConsoleTitle(title): + return _SetConsoleTitleW(title) + + def GetConsoleMode(handle): + mode = wintypes.DWORD() + success = _GetConsoleMode(handle, byref(mode)) + if not success: + raise ctypes.WinError() + return mode.value + + def SetConsoleMode(handle, mode): + success = _SetConsoleMode(handle, mode) + if not success: + raise ctypes.WinError() diff --git a/lib/colorama/winterm.py b/lib/colorama/winterm.py new file mode 100644 index 0000000..aad867e --- /dev/null +++ b/lib/colorama/winterm.py @@ -0,0 +1,195 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +try: + from msvcrt import get_osfhandle +except ImportError: + def get_osfhandle(_): + raise OSError("This isn't windows!") + + +from . import win32 + +# from wincon.h +class WinColor(object): + BLACK = 0 + BLUE = 1 + GREEN = 2 + CYAN = 3 + RED = 4 + MAGENTA = 5 + YELLOW = 6 + GREY = 7 + +# from wincon.h +class WinStyle(object): + NORMAL = 0x00 # dim text, dim background + BRIGHT = 0x08 # bright text, dim background + BRIGHT_BACKGROUND = 0x80 # dim text, bright background + +class WinTerm(object): + + def __init__(self): + self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes + self.set_attrs(self._default) + self._default_fore = self._fore + self._default_back = self._back + self._default_style = self._style + # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style. + # So that LIGHT_EX colors and BRIGHT style do not clobber each other, + # we track them separately, since LIGHT_EX is overwritten by Fore/Back + # and BRIGHT is overwritten by Style codes. + self._light = 0 + + def get_attrs(self): + return self._fore + self._back * 16 + (self._style | self._light) + + def set_attrs(self, value): + self._fore = value & 7 + self._back = (value >> 4) & 7 + self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND) + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + self._light = 0 + + def fore(self, fore=None, light=False, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + # Emulate LIGHT_EX with BRIGHT Style + if light: + self._light |= WinStyle.BRIGHT + else: + self._light &= ~WinStyle.BRIGHT + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, light=False, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style + if light: + self._light |= WinStyle.BRIGHT_BACKGROUND + else: + self._light &= ~WinStyle.BRIGHT_BACKGROUND + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + # I'm not currently tracking the position, so there is no default. + # position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_adjust(self, x, y, on_stderr=False): + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y + y, position.X + x) + win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False) + + def erase_screen(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen, and move cursor to (1,1) + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y + # get number of character cells before current cursor position + cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = cells_in_screen - cells_before_cursor + elif mode == 1: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_before_cursor + elif mode == 2: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_in_screen + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + if mode == 2: + # put the cursor where needed + win32.SetConsoleCursorPosition(handle, (1, 1)) + + def erase_line(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the line. + # 1 should clear from the cursor to the beginning of the line. + # 2 should clear the entire line. + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X + elif mode == 1: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwCursorPosition.X + elif mode == 2: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwSize.X + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + + def set_title(self, title): + win32.SetConsoleTitle(title) + + +def enable_vt_processing(fd): + if win32.windll is None or not win32.winapi_test(): + return False + + try: + handle = get_osfhandle(fd) + mode = win32.GetConsoleMode(handle) + win32.SetConsoleMode( + handle, + mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ) + + mode = win32.GetConsoleMode(handle) + if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING: + return True + # Can get TypeError in testsuite where 'fd' is a Mock() + except (OSError, TypeError): + return False diff --git a/lib/frozenlist-1.3.3.dist-info/INSTALLER b/lib/frozenlist-1.3.3.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/frozenlist-1.3.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/frozenlist-1.3.3.dist-info/LICENSE b/lib/frozenlist-1.3.3.dist-info/LICENSE new file mode 100644 index 0000000..7082a2d --- /dev/null +++ b/lib/frozenlist-1.3.3.dist-info/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013-2019 Nikolay Kim and Andrew Svetlov + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/frozenlist-1.3.3.dist-info/METADATA b/lib/frozenlist-1.3.3.dist-info/METADATA new file mode 100644 index 0000000..235aca1 --- /dev/null +++ b/lib/frozenlist-1.3.3.dist-info/METADATA @@ -0,0 +1,150 @@ +Metadata-Version: 2.1 +Name: frozenlist +Version: 1.3.3 +Summary: A list-like structure which implements collections.abc.MutableSequence +Home-page: https://github.com/aio-libs/frozenlist +Maintainer: aiohttp team +Maintainer-email: team@aiohttp.org +License: Apache 2 +Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby +Project-URL: CI: Github Actions, https://github.com/aio-libs/frozenlist/actions +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/frozenlist +Project-URL: Docs: RTD, https://frozenlist.readthedocs.io +Project-URL: GitHub: issues, https://github.com/aio-libs/frozenlist/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/frozenlist +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE + +========== +frozenlist +========== + +.. image:: https://github.com/aio-libs/frozenlist/workflows/CI/badge.svg + :target: https://github.com/aio-libs/frozenlist/actions + :alt: GitHub status for master branch + +.. image:: https://codecov.io/gh/aio-libs/frozenlist/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/frozenlist + :alt: codecov.io status for master branch + +.. image:: https://badge.fury.io/py/frozenlist.svg + :target: https://pypi.org/project/frozenlist + :alt: Latest PyPI package version + +.. image:: https://readthedocs.org/projects/frozenlist/badge/?version=latest + :target: https://frozenlist.readthedocs.io/ + :alt: Latest Read The Docs + +.. image:: https://img.shields.io/discourse/topics?server=https%3A%2F%2Faio-libs.discourse.group%2F + :target: https://aio-libs.discourse.group/ + :alt: Discourse group for io-libs + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :target: https://gitter.im/aio-libs/Lobby + :alt: Chat on Gitter + +Introduction +============ + +``frozenlist.FrozenList`` is a list-like structure which implements +``collections.abc.MutableSequence``. The list is *mutable* until ``FrozenList.freeze`` +is called, after which list modifications raise ``RuntimeError``: + + +>>> from frozenlist import FrozenList +>>> fl = FrozenList([17, 42]) +>>> fl.append('spam') +>>> fl.append('Vikings') +>>> fl + +>>> fl.freeze() +>>> fl + +>>> fl.frozen +True +>>> fl.append("Monty") +Traceback (most recent call last): + File "", line 1, in + File "frozenlist/_frozenlist.pyx", line 97, in frozenlist._frozenlist.FrozenList.append + self._check_frozen() + File "frozenlist/_frozenlist.pyx", line 19, in frozenlist._frozenlist.FrozenList._check_frozen + raise RuntimeError("Cannot modify frozen list.") +RuntimeError: Cannot modify frozen list. + + +FrozenList is also hashable, but only when frozen. Otherwise it also throws a RuntimeError: + + +>>> fl = FrozenList([17, 42, 'spam']) +>>> hash(fl) +Traceback (most recent call last): + File "", line 1, in + File "frozenlist/_frozenlist.pyx", line 111, in frozenlist._frozenlist.FrozenList.__hash__ + raise RuntimeError("Cannot hash unfrozen list.") +RuntimeError: Cannot hash unfrozen list. +>>> fl.freeze() +>>> hash(fl) +3713081631934410656 +>>> dictionary = {fl: 'Vikings'} # frozen fl can be a dict key +>>> dictionary +{: 'Vikings'} + + +Installation +------------ + +:: + + $ pip install frozenlist + +The library requires Python 3.6 or newer. + + +Documentation +============= + +https://frozenlist.readthedocs.io/ + +Communication channels +====================== + +*aio-libs discourse group*: https://aio-libs.discourse.group + +Feel free to post your questions and ideas here. + +*gitter chat* https://gitter.im/aio-libs/Lobby + +Requirements +============ + +- Python >= 3.6 + +License +======= + +``frozenlist`` is offered under the Apache 2 license. + +Source code +=========== + +The project is hosted on GitHub_ + +Please file an issue in the `bug tracker +`_ if you have found a bug +or have some suggestions to improve the library. + +.. _GitHub: https://github.com/aio-libs/frozenlist diff --git a/lib/frozenlist-1.3.3.dist-info/RECORD b/lib/frozenlist-1.3.3.dist-info/RECORD new file mode 100644 index 0000000..bf95b73 --- /dev/null +++ b/lib/frozenlist-1.3.3.dist-info/RECORD @@ -0,0 +1,12 @@ +frozenlist-1.3.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +frozenlist-1.3.3.dist-info/LICENSE,sha256=atcq6P9K6Td0Wq4oBfNDqYf6o6YGrHLGCfLUj3GZspQ,11533 +frozenlist-1.3.3.dist-info/METADATA,sha256=40xSh5BwSzbvvFSx5jCmfh3ysisPkMTRnMIduCPMLu8,4843 +frozenlist-1.3.3.dist-info/RECORD,, +frozenlist-1.3.3.dist-info/WHEEL,sha256=b_zGOvkdNUsRV8EolCx_oXCW11WesEBWSFFw8VOXkRs,100 +frozenlist-1.3.3.dist-info/top_level.txt,sha256=jivtxsPXA3nK3WBWW2LW5Mtu_GHt8UZA13NeCs2cKuA,11 +frozenlist/__init__.py,sha256=5CM0q36rzVuDHZ0euopeBqLNjN8lFKzaQUkAkRgjTgE,2419 +frozenlist/__init__.pyi,sha256=Ja2qc1nzmMYOqbgnfvB1q8FHgs1VPU6t2MyCZ_kUP8o,1517 +frozenlist/__pycache__/__init__.cpython-39.pyc,, +frozenlist/_frozenlist.cp39-win_amd64.pyd,sha256=XTu0Y_-YjgwFgEUhPbueKFqvW8t4sIA-Djf-6ji3bOg,53760 +frozenlist/_frozenlist.pyx,sha256=IrNkJISUDLioWNUpIDj2VfxN_RFiJ6gHlv47rYBNgwQ,3106 +frozenlist/py.typed,sha256=3VVwXUAWVEVX7sDwyYDnW5ZdBC9_Z9AJAFfLCleUW0k,8 diff --git a/lib/frozenlist-1.3.3.dist-info/WHEEL b/lib/frozenlist-1.3.3.dist-info/WHEEL new file mode 100644 index 0000000..f376627 --- /dev/null +++ b/lib/frozenlist-1.3.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.2) +Root-Is-Purelib: false +Tag: cp39-cp39-win_amd64 + diff --git a/lib/frozenlist-1.3.3.dist-info/top_level.txt b/lib/frozenlist-1.3.3.dist-info/top_level.txt new file mode 100644 index 0000000..52f13fc --- /dev/null +++ b/lib/frozenlist-1.3.3.dist-info/top_level.txt @@ -0,0 +1 @@ +frozenlist diff --git a/lib/frozenlist/__init__.py b/lib/frozenlist/__init__.py new file mode 100644 index 0000000..4f172e7 --- /dev/null +++ b/lib/frozenlist/__init__.py @@ -0,0 +1,96 @@ +import os +import sys +import types +from collections.abc import MutableSequence +from functools import total_ordering +from typing import Tuple, Type + +__version__ = "1.3.3" + +__all__ = ("FrozenList", "PyFrozenList") # type: Tuple[str, ...] + + +NO_EXTENSIONS = bool(os.environ.get("FROZENLIST_NO_EXTENSIONS")) # type: bool + + +@total_ordering +class FrozenList(MutableSequence): + + __slots__ = ("_frozen", "_items") + + if sys.version_info >= (3, 9): + __class_getitem__ = classmethod(types.GenericAlias) + else: + + @classmethod + def __class_getitem__(cls: Type["FrozenList"]) -> Type["FrozenList"]: + return cls + + def __init__(self, items=None): + self._frozen = False + if items is not None: + items = list(items) + else: + items = [] + self._items = items + + @property + def frozen(self): + return self._frozen + + def freeze(self): + self._frozen = True + + def __getitem__(self, index): + return self._items[index] + + def __setitem__(self, index, value): + if self._frozen: + raise RuntimeError("Cannot modify frozen list.") + self._items[index] = value + + def __delitem__(self, index): + if self._frozen: + raise RuntimeError("Cannot modify frozen list.") + del self._items[index] + + def __len__(self): + return self._items.__len__() + + def __iter__(self): + return self._items.__iter__() + + def __reversed__(self): + return self._items.__reversed__() + + def __eq__(self, other): + return list(self) == other + + def __le__(self, other): + return list(self) <= other + + def insert(self, pos, item): + if self._frozen: + raise RuntimeError("Cannot modify frozen list.") + self._items.insert(pos, item) + + def __repr__(self): + return f"" + + def __hash__(self): + if self._frozen: + return hash(tuple(self)) + else: + raise RuntimeError("Cannot hash unfrozen list.") + + +PyFrozenList = FrozenList + + +try: + from ._frozenlist import FrozenList as CFrozenList # type: ignore + + if not NO_EXTENSIONS: # pragma: no cover + FrozenList = CFrozenList # type: ignore +except ImportError: # pragma: no cover + pass diff --git a/lib/frozenlist/__init__.pyi b/lib/frozenlist/__init__.pyi new file mode 100644 index 0000000..ae803ef --- /dev/null +++ b/lib/frozenlist/__init__.pyi @@ -0,0 +1,47 @@ +from typing import ( + Generic, + Iterable, + Iterator, + List, + MutableSequence, + Optional, + TypeVar, + Union, + overload, +) + +_T = TypeVar("_T") +_Arg = Union[List[_T], Iterable[_T]] + +class FrozenList(MutableSequence[_T], Generic[_T]): + def __init__(self, items: Optional[_Arg[_T]] = None) -> None: ... + @property + def frozen(self) -> bool: ... + def freeze(self) -> None: ... + @overload + def __getitem__(self, i: int) -> _T: ... + @overload + def __getitem__(self, s: slice) -> FrozenList[_T]: ... + @overload + def __setitem__(self, i: int, o: _T) -> None: ... + @overload + def __setitem__(self, s: slice, o: Iterable[_T]) -> None: ... + @overload + def __delitem__(self, i: int) -> None: ... + @overload + def __delitem__(self, i: slice) -> None: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T]: ... + def __reversed__(self) -> Iterator[_T]: ... + def __eq__(self, other: object) -> bool: ... + def __le__(self, other: FrozenList[_T]) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __lt__(self, other: FrozenList[_T]) -> bool: ... + def __ge__(self, other: FrozenList[_T]) -> bool: ... + def __gt__(self, other: FrozenList[_T]) -> bool: ... + def insert(self, pos: int, item: _T) -> None: ... + def __repr__(self) -> str: ... + def __hash__(self) -> int: ... + +# types for C accelerators are the same +CFrozenList = PyFrozenList = FrozenList diff --git a/lib/frozenlist/__pycache__/__init__.cpython-39.pyc b/lib/frozenlist/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d64f1e1b7f7c84f8cc5c7451b3cac04b6970fc37 GIT binary patch literal 3274 zcmbVOTW=Fb6rPz~JL^kg#|gO+AO+gumWCE8RYif)rU@W{K{!?0m8H?fGfAARcg@Ti zHI{j4qW*}|O5rgNeeZAC*FNPh@YHj5eTx%U6>IHWc4mCe`ObIF9A|tyP2hR-*Sydv zLjJ+Y;A6q$0lfMM8bJhgi5>UUrqP_)40`4ob<;K}t{HBkp0tzE*mTG0mTlEjb_(VR zJ1vrSMrI}19Y2~7V{hpJwR1B0kO)hp4v9!f^T2>_d6~B-TXP~UGH#)sx2GtPIsKcQ zvhzO@uV4~6z5hEU`1_f7>~qkpOp4hP(2|sjG|WA12F|7{%kpJYdQ}OZCIUZjT<$9& zRn6NjvM{mHY`8KsH})H{mH1-$&hj1jnqF6aOL|XgIta7R_xm$@ZvDkGd#&{3ae0H6 zp7FJxHr7hz$InXTDvBL^Oqe`?SN{!-BnTD2L~TQwy9vSe66&f<%CTJwOig)e=uQ+#G#3+;+hx})*(gML`tL&349$F8Ie6C_JkN06VT^GPUNA_i%C&{eo{<{ zY3K{$f|!ARO3aEm=%>ZJSb+Y5xF{B(pAnbDW$0(c6|n^UocKUog??Tn0nY0x3F;rS zaExz7n1o4Q3uIjv*@*!wC%_m2h#6H^he;)art+#3zI7iQfm*8tuZJ@ZcEV z$c&F*AZVL#(gv?KshSID8)*ERdgeBH4P#h?kO2HxX}xGD1SvF8JB6_Zc$r$dTVW!C zvX6k8#En+hf=e*dQE<{EwP>1|v^8}~=h)KGfTQ!;WEV71IT#+(NEPU&VKyu`8H4yC zO6?#tIUp@wJtM1!3Mv|-(y@F)&7zdK^F>M7lEt1V+Bz)?S4TSlMWI=3I^Q)7*o0q? zS!5a@>k0YGu!R}kC&JAE*)s!X4s59+!@CcT@r@X;2m@3=O$1bc=YOSb26NV?E0F$x z;H=bISam$l50>h_sBP^p#qcd536_sLl*qnldO@u&*Oc;AObsJ;4GIVouN=22&oM%G z*?bGOX>>A)7}7dzNN?L{Bp2Z5kRg#7_aZaYWmr~Mu(^m$FH~cq0-Qp+aa?@~>!UU4 zZToNfpe9H3ZO^AH!US>;ccsVqc~nx@LGafoJfbXZ6;AmwI`>{eF$x2zMo7gVSs9Qz z6qeD!_Y#{0f$|mPKq*Fu#_aRWfauv%2KNt-c6t_02jKPt$W(i*pBtv&doO_eU`MK9 zk0&{oFFF5iK>T3b#;gBRFx=KrQc@qozo{G84Dk!3xsvm>k@&`^z;oA#-9neI?PgQYNhYn8*u`;*J3x9TC8|qVO@4!MBx3Q zWCNJ{6cy`)#c6`Z>u1#J*0%6M!Gf`zwm%)xNKtm`1VaG%1L1|e1)~z{O6%b)` zl|na#Rzvv>V8y<|B0^#6RCqPRv0{y$sr{7E?gWlu;aRbqqzxZ!ZGQ+ zswv+K4XCT^@l%Cxq=g!T|FJWjni20jJG0v7a+rBsZ}=*R@4a)t2(6Xar}xn`jR0Fv Z0a-K)zdSRUY2*Q$!nz4<7V08g`4@J4xU>KO literal 0 HcmV?d00001 diff --git a/lib/frozenlist/_frozenlist.cp39-win_amd64.pyd b/lib/frozenlist/_frozenlist.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..ecdb680418847fd76aab721dc4be016a44b24166 GIT binary patch literal 53760 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�-&!}t?Ut0=Ko9 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 (3, 9): + __class_getitem__ = classmethod(types.GenericAlias) + else: + @classmethod + def __class_getitem__(cls): + return cls + + cdef readonly bint frozen + cdef list _items + + def __init__(self, items=None): + self.frozen = False + if items is not None: + items = list(items) + else: + items = [] + self._items = items + + cdef object _check_frozen(self): + if self.frozen: + raise RuntimeError("Cannot modify frozen list.") + + cdef inline object _fast_len(self): + return len(self._items) + + def freeze(self): + self.frozen = True + + def __getitem__(self, index): + return self._items[index] + + def __setitem__(self, index, value): + self._check_frozen() + self._items[index] = value + + def __delitem__(self, index): + self._check_frozen() + del self._items[index] + + def __len__(self): + return self._fast_len() + + def __iter__(self): + return self._items.__iter__() + + def __reversed__(self): + return self._items.__reversed__() + + def __richcmp__(self, other, op): + if op == 0: # < + return list(self) < other + if op == 1: # <= + return list(self) <= other + if op == 2: # == + return list(self) == other + if op == 3: # != + return list(self) != other + if op == 4: # > + return list(self) > other + if op == 5: # => + return list(self) >= other + + def insert(self, pos, item): + self._check_frozen() + self._items.insert(pos, item) + + def __contains__(self, item): + return item in self._items + + def __iadd__(self, items): + self._check_frozen() + self._items += list(items) + return self + + def index(self, item): + return self._items.index(item) + + def remove(self, item): + self._check_frozen() + self._items.remove(item) + + def clear(self): + self._check_frozen() + self._items.clear() + + def extend(self, items): + self._check_frozen() + self._items += list(items) + + def reverse(self): + self._check_frozen() + self._items.reverse() + + def pop(self, index=-1): + self._check_frozen() + return self._items.pop(index) + + def append(self, item): + self._check_frozen() + return self._items.append(item) + + def count(self, item): + return self._items.count(item) + + def __repr__(self): + return ''.format(self.frozen, + self._items) + + def __hash__(self): + if self.frozen: + return hash(tuple(self._items)) + else: + raise RuntimeError("Cannot hash unfrozen list.") + + +MutableSequence.register(FrozenList) diff --git a/lib/frozenlist/py.typed b/lib/frozenlist/py.typed new file mode 100644 index 0000000..f5642f7 --- /dev/null +++ b/lib/frozenlist/py.typed @@ -0,0 +1 @@ +Marker diff --git a/lib/idna-3.4.dist-info/INSTALLER b/lib/idna-3.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/idna-3.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/idna-3.4.dist-info/LICENSE.md b/lib/idna-3.4.dist-info/LICENSE.md new file mode 100644 index 0000000..b6f8732 --- /dev/null +++ b/lib/idna-3.4.dist-info/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2013-2021, Kim Davies +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/idna-3.4.dist-info/METADATA b/lib/idna-3.4.dist-info/METADATA new file mode 100644 index 0000000..07f6193 --- /dev/null +++ b/lib/idna-3.4.dist-info/METADATA @@ -0,0 +1,242 @@ +Metadata-Version: 2.1 +Name: idna +Version: 3.4 +Summary: Internationalized Domain Names in Applications (IDNA) +Author-email: Kim Davies +Requires-Python: >=3.5 +Description-Content-Type: text/x-rst +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: Name Service (DNS) +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Utilities +Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.rst +Project-URL: Issue tracker, https://github.com/kjd/idna/issues +Project-URL: Source, https://github.com/kjd/idna + +Internationalized Domain Names in Applications (IDNA) +===================================================== + +Support for the Internationalized Domain Names in +Applications (IDNA) protocol as specified in `RFC 5891 +`_. This is the latest version of +the protocol and is sometimes referred to as “IDNA 2008”. + +This library also provides support for Unicode Technical +Standard 46, `Unicode IDNA Compatibility Processing +`_. + +This acts as a suitable replacement for the “encodings.idna” +module that comes with the Python standard library, but which +only supports the older superseded IDNA specification (`RFC 3490 +`_). + +Basic functions are simply executed: + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('ドメイン.テスト') + b'xn--eckwd4c7c.xn--zckzah' + >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) + ドメイン.テスト + + +Installation +------------ + +This package is available for installation from PyPI: + +.. code-block:: bash + + $ python3 -m pip install idna + + +Usage +----- + +For typical usage, the ``encode`` and ``decode`` functions will take a +domain name argument and perform a conversion to A-labels or U-labels +respectively. + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('ドメイン.テスト') + b'xn--eckwd4c7c.xn--zckzah' + >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) + ドメイン.テスト + +You may use the codec encoding and decoding methods using the +``idna.codec`` module: + +.. code-block:: pycon + + >>> import idna.codec + >>> print('домен.испытание'.encode('idna')) + b'xn--d1acufc.xn--80akhbyknj4f' + >>> print(b'xn--d1acufc.xn--80akhbyknj4f'.decode('idna')) + домен.испытание + +Conversions can be applied at a per-label basis using the ``ulabel`` or +``alabel`` functions if necessary: + +.. code-block:: pycon + + >>> idna.alabel('测试') + b'xn--0zwm56d' + +Compatibility Mapping (UTS #46) ++++++++++++++++++++++++++++++++ + +As described in `RFC 5895 `_, the +IDNA specification does not normalize input from different potential +ways a user may input a domain name. This functionality, known as +a “mapping”, is considered by the specification to be a local +user-interface issue distinct from IDNA conversion functionality. + +This library provides one such mapping, that was developed by the +Unicode Consortium. Known as `Unicode IDNA Compatibility Processing +`_, it provides for both a regular +mapping for typical applications, as well as a transitional mapping to +help migrate from older IDNA 2003 applications. + +For example, “Königsgäßchen” is not a permissible label as *LATIN +CAPITAL LETTER K* is not allowed (nor are capital letters in general). +UTS 46 will convert this into lower case prior to applying the IDNA +conversion. + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('Königsgäßchen') + ... + idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed + >>> idna.encode('Königsgäßchen', uts46=True) + b'xn--knigsgchen-b4a3dun' + >>> print(idna.decode('xn--knigsgchen-b4a3dun')) + königsgäßchen + +Transitional processing provides conversions to help transition from +the older 2003 standard to the current standard. For example, in the +original IDNA specification, the *LATIN SMALL LETTER SHARP S* (ß) was +converted into two *LATIN SMALL LETTER S* (ss), whereas in the current +IDNA specification this conversion is not performed. + +.. code-block:: pycon + + >>> idna.encode('Königsgäßchen', uts46=True, transitional=True) + 'xn--knigsgsschen-lcb0w' + +Implementors should use transitional processing with caution, only in +rare cases where conversion from legacy labels to current labels must be +performed (i.e. IDNA implementations that pre-date 2008). For typical +applications that just need to convert labels, transitional processing +is unlikely to be beneficial and could produce unexpected incompatible +results. + +``encodings.idna`` Compatibility +++++++++++++++++++++++++++++++++ + +Function calls from the Python built-in ``encodings.idna`` module are +mapped to their IDNA 2008 equivalents using the ``idna.compat`` module. +Simply substitute the ``import`` clause in your code to refer to the new +module name. + +Exceptions +---------- + +All errors raised during the conversion following the specification +should raise an exception derived from the ``idna.IDNAError`` base +class. + +More specific exceptions that may be generated as ``idna.IDNABidiError`` +when the error reflects an illegal combination of left-to-right and +right-to-left characters in a label; ``idna.InvalidCodepoint`` when +a specific codepoint is an illegal character in an IDN label (i.e. +INVALID); and ``idna.InvalidCodepointContext`` when the codepoint is +illegal based on its positional context (i.e. it is CONTEXTO or CONTEXTJ +but the contextual requirements are not satisfied.) + +Building and Diagnostics +------------------------ + +The IDNA and UTS 46 functionality relies upon pre-calculated lookup +tables for performance. These tables are derived from computing against +eligibility criteria in the respective standards. These tables are +computed using the command-line script ``tools/idna-data``. + +This tool will fetch relevant codepoint data from the Unicode repository +and perform the required calculations to identify eligibility. There are +three main modes: + +* ``idna-data make-libdata``. Generates ``idnadata.py`` and + ``uts46data.py``, the pre-calculated lookup tables using for IDNA and + UTS 46 conversions. Implementors who wish to track this library against + a different Unicode version may use this tool to manually generate a + different version of the ``idnadata.py`` and ``uts46data.py`` files. + +* ``idna-data make-table``. Generate a table of the IDNA disposition + (e.g. PVALID, CONTEXTJ, CONTEXTO) in the format found in Appendix + B.1 of RFC 5892 and the pre-computed tables published by `IANA + `_. + +* ``idna-data U+0061``. Prints debugging output on the various + properties associated with an individual Unicode codepoint (in this + case, U+0061), that are used to assess the IDNA and UTS 46 status of a + codepoint. This is helpful in debugging or analysis. + +The tool accepts a number of arguments, described using ``idna-data +-h``. Most notably, the ``--version`` argument allows the specification +of the version of Unicode to use in computing the table data. For +example, ``idna-data --version 9.0.0 make-libdata`` will generate +library data against Unicode 9.0.0. + + +Additional Notes +---------------- + +* **Packages**. The latest tagged release version is published in the + `Python Package Index `_. + +* **Version support**. This library supports Python 3.5 and higher. + As this library serves as a low-level toolkit for a variety of + applications, many of which strive for broad compatibility with older + Python versions, there is no rush to remove older intepreter support. + Removing support for older versions should be well justified in that the + maintenance burden has become too high. + +* **Python 2**. Python 2 is supported by version 2.x of this library. + While active development of the version 2.x series has ended, notable + issues being corrected may be backported to 2.x. Use "idna<3" in your + requirements file if you need this library for a Python 2 application. + +* **Testing**. The library has a test suite based on each rule of the + IDNA specification, as well as tests that are provided as part of the + Unicode Technical Standard 46, `Unicode IDNA Compatibility Processing + `_. + +* **Emoji**. It is an occasional request to support emoji domains in + this library. Encoding of symbols like emoji is expressly prohibited by + the technical standard IDNA 2008 and emoji domains are broadly phased + out across the domain industry due to associated security risks. For + now, applications that wish need to support these non-compliant labels + may wish to consider trying the encode/decode operation in this library + first, and then falling back to using `encodings.idna`. See `the Github + project `_ for more discussion. + diff --git a/lib/idna-3.4.dist-info/RECORD b/lib/idna-3.4.dist-info/RECORD new file mode 100644 index 0000000..0139bf5 --- /dev/null +++ b/lib/idna-3.4.dist-info/RECORD @@ -0,0 +1,22 @@ +idna-3.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +idna-3.4.dist-info/LICENSE.md,sha256=otbk2UC9JNvnuWRc3hmpeSzFHbeuDVrNMBrIYMqj6DY,1523 +idna-3.4.dist-info/METADATA,sha256=8aLSf9MFS7oB26pZh2hprg7eJp0UJSc-3rpf_evp4DA,9830 +idna-3.4.dist-info/RECORD,, +idna-3.4.dist-info/WHEEL,sha256=4TfKIB_xu-04bc2iKz6_zFt-gEFEEDU_31HGhqzOCE8,81 +idna/__init__.py,sha256=KJQN1eQBr8iIK5SKrJ47lXvxG0BJ7Lm38W4zT0v_8lk,849 +idna/__pycache__/__init__.cpython-39.pyc,, +idna/__pycache__/codec.cpython-39.pyc,, +idna/__pycache__/compat.cpython-39.pyc,, +idna/__pycache__/core.cpython-39.pyc,, +idna/__pycache__/idnadata.cpython-39.pyc,, +idna/__pycache__/intranges.cpython-39.pyc,, +idna/__pycache__/package_data.cpython-39.pyc,, +idna/__pycache__/uts46data.cpython-39.pyc,, +idna/codec.py,sha256=6ly5odKfqrytKT9_7UrlGklHnf1DSK2r9C6cSM4sa28,3374 +idna/compat.py,sha256=0_sOEUMT4CVw9doD3vyRhX80X19PwqFoUBs7gWsFME4,321 +idna/core.py,sha256=1JxchwKzkxBSn7R_oCE12oBu3eVux0VzdxolmIad24M,12950 +idna/idnadata.py,sha256=xUjqKqiJV8Ho_XzBpAtv5JFoVPSupK-SUXvtjygUHqw,44375 +idna/intranges.py,sha256=YBr4fRYuWH7kTKS2tXlFjM24ZF1Pdvcir-aywniInqg,1881 +idna/package_data.py,sha256=C_jHJzmX8PI4xq0jpzmcTMxpb5lDsq4o5VyxQzlVrZE,21 +idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +idna/uts46data.py,sha256=zvjZU24s58_uAS850Mcd0NnD0X7_gCMAMjzWNIeUJdc,206539 diff --git a/lib/idna-3.4.dist-info/WHEEL b/lib/idna-3.4.dist-info/WHEEL new file mode 100644 index 0000000..668ba4d --- /dev/null +++ b/lib/idna-3.4.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.7.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/lib/idna/__init__.py b/lib/idna/__init__.py new file mode 100644 index 0000000..a40eeaf --- /dev/null +++ b/lib/idna/__init__.py @@ -0,0 +1,44 @@ +from .package_data import __version__ +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .intranges import intranges_contain + +__all__ = [ + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/lib/idna/__pycache__/__init__.cpython-39.pyc b/lib/idna/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec47b2a54048aa1634f8507d7a21972b7a52bcd8 GIT binary patch literal 824 zcmbu7&5qMB5P*~Zq)n6lce}tF^Z-Z*aX<(GT_hHv6==o9ie$O5Nli0OWV`$6!`V#`(6H_8w5X4`FnM|^1^RpJ$S&wGJueT zA|9;3Qu1xZMO{f{-RW)1wo+Dnu`XgI+lkVR^1a9pbm!f%CC4I=FRf zkV;y~N}5-&lS*ipwD#6R+$!o*?jkO7AKFDiwclg=OLDa4@e`^Au-Z+U6uq-1kAj!SEnA6OwMXO?Lk6xYiuE&eO?Kcl+%Po+(cZa^t3 zOG>{5`W~i+`1N-CH2Y}$q-2-5mUVU}%)wT5R@9c7J>x=WRv7yzdnb39kyhNlDa2%w gEB265>@=kh>T5TA1{FV+;yKQs!ADHP_p7w?8x_#sn*aa+ literal 0 HcmV?d00001 diff --git a/lib/idna/__pycache__/codec.cpython-39.pyc b/lib/idna/__pycache__/codec.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e236d485bc4c4a9a74cf70858dff07ef5b3e10e GIT binary patch literal 3061 zcmds3&2Jk;6yMpM{jg&vsgt&(ls2ivhmlC3fRIpI6`HD4LPnw#q{U^m@r=_=*K7Cf zSSV#9RZXw(7xa)Eb3o$A0sfJ>_S91koahDKn~h_q2|^%oVXS>S@AqbB-n{v}H;(J| zs)g;79~Z=O-LjsLGTB^IuHcjZj6zs~rB;uHtY?RI&k3C#4|&fGU1kYeIH?zU=Iw_* zzIht-Dq*Ep4XezO{`Qn`cUV}vZV6BLk1XL!Z@s?dgmpv$Q9-1F$TT7|h*ZTCB2$RW zB619ony4dEmvzJ%TRdzaHZ5iln=#lNVsmJl7qgFnPw@k*)wH$y#c+^H9eg%WNk5CzVGRvmFF$$k z@YydvJo|O$c85kzHV>66_~a`nq!lt@g|-N!gFh}^>2>V|jMrmFID$WNF;*2s$>vPh zT;ZWD5I*`})$UbvyQ95aLDEr8M{^OY823ZI=Gp_PhLCj}TyfN>&+)m6Pri(zum!v8 zzzKzmZ?nSQVO#7WD_DMpf~4#Mb>)!k+PElh!4!V={OqkP9}Wh6P*NA>U`h)h`Kt=hTSV(9qnX3!!Pm|Slo+$a&Anx3a*JR#KL>8Z?bL=b+ zHq2T?=G-uv1#H^pjNA8XCUnc?$^RD-f*IP5tq+6%jv)wi2GAfeM}jt7iWAf$E8&0P zammqElbLfEy(&70qAc#oDALs^>h;AirG71n?hWH~&*C6aAi%k`ySjn#(@v6=$5Hw; z_QdpzI}|tiBq!Dc_UQ@`(!u9I+o$&%2#@H!Pwo+g*<((n08`Kx9NFaR#*M54vL`bY zr`Jpx!Y&{4LA%`q(+j3?)(&|vYUBi!A;aUJu=963-bN1ZuuU%brdx3Mvf#p9z-$yA z>B7BaWsSng4Sva@5oB4jHnA*i`UPLHZoj!16y7#hZV_zZT+Awbcu@Ev_`t#uzVZmC z{=HS{UqD1!aO1S2?k53YR?q5T-OxD3FRzJ zNA)5frV4X1xsMZ@F=4d?BcjfjA4Cm39jy+#T?tW{MXw;1zm3A$PqQk+WJAq7wnrMJ zAK6RkOtTuhzjT-c%LnIfH3L|{nc)XIWs6-w~86bvEFy_KRFUO2|2Mw3Vzdgo~euy!YpjT)RCD^F!35KQ3 z*0q}oWZe4-@OpyQuJa;+$DtW7`YulM4b8W=DCz%Z(a$B_ku7)+zn?gR@j~yTVRd35 z((y7ebr!n^S_L&zXBV-ftaT=svbU^t$4IP)%2U1yg#A7JEpb|!(;eLOHK%*WT*2mk z(T!wW?8}+~2e9(sK-aL?VC23vksE1<;Fg*d?U~Cx*A;Vx-^lP^1>FYYbxd*toflC2 zfcAD&5cJfV#D&)I@_~&m4$A3Lwy;9k%)_5u{(c9dY4e2Uojy!#r>wPx^d==P;wWun zN%qvn0BdSVG76sdJN@1uNhO$!A#e4mk4^2A;&AOV{vSDDkLfS&jc96|JaU)8#q#gK bWg=@%*THElzyX`Jt9E0)vD8?a=kxqehi8Js literal 0 HcmV?d00001 diff --git a/lib/idna/__pycache__/compat.cpython-39.pyc b/lib/idna/__pycache__/compat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca9f13ccfea0e3bb10c224e9c82973d87675ffe9 GIT binary patch literal 743 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

RMH?Ui)Asz2xB z$^<85m{brEv$aMblocpnA%RER&7AH@<*oyYe1sK;JMM{ggW{m1CY6f}0tVbM88IO8 z;c0~XU&cAe02zfmuFGpIRGN`&AZx6a?;wfyLGn8DH*=&6k+%anDkS{Ki3VQqB5Xox z!yLE}u;3}0DI+Q68CtcRDq4^>wME=xaV+BbvcyGEsGN>L;tX=z{!r@s&O`0k)sTD8 zJI|N6Th(|15Xyv`LbXsI9 znO24;@0BbZT{&zlp+pNXKz;`KxmK2Lyzm2jY1h5mG9=?T4ahZsZLO6Pc0)tlYS}Mp zD^_q!<-lEsTCnlLpSElN1a4BsN#zqz`2;O;s>Kj;2XPwwG_qHWrILMp5iWfp<%ifJrqrPY50t@L`0YGmu-qj?rJ>!SCM~erNFTB08$%!S-oehL{LtSzt z`hp&z9&JK@hWr%-#R0j(IM3no*BRVI5Zkpz1bHdd@xtPTOPr%%EUOE$IHP7FYu95N z+z>UwcmU3(DwH;_@%r$NOBv3)j5|KJ&a-cL8C_`5FNw`Ze2?5A@-4*$@ci5{bmNX; z*k2lkffrx+g$n)o$RoFqf8bNPW8BGG@_kUodCkFxW0O9X2i6Pv@0sE#N-I1VbsqY; zyNc}Jkm!}M@=%YA?Z`B1`CuDVsYFV)m6##VP-%fdtW|S`jFpEPx`(;F^Q$oqitmvKabXFLsKbPs&pSu)rP5*T{}EhUkoJjRxSQTi{an^!&YU_IEDzNf?a zTBC|QdwLH#+CTx-p8r1UiBkf;I(f8 zc?A;!drGm!wp*`5AH){TN_7tclfXec`Fjk$$6$jMc)!9+&9G68Vq1+jOb$PN5}lf2 zipZu9c|)QPDMZub31s73Lm5u1iqc)79eEZI%pasXsT5Q99F9V)@;9*qJe+VBv9t=B zyEkEWSJx`|%7}&Ol_1svVxiZ=x1;nLjzU1G9ROo%8HHZEWdpJHU5K@b*zkKONze*G zuF#rJ1g=8sU$#jy1M{yj@XN}<&PS`h;-b8TFXb#?W{iL+XZC4rY4KlB%Y$l;OS(Z27CT32FfdK zNH4bGv4Wo-9X!u`LWn5a50Qf}FlH z*OrB6uQ7OwfjYJXj4d&siJL4pIW{q~)ZT2Y zq>2rrG|n7J{uAaXWrXsb(Nf~CKzoV&V-!;DUwXtWm<4Oh-er%zRIm$~LTcoh?N9G| OxG=Ws!R^kTNB$Sn67)0x literal 0 HcmV?d00001 diff --git a/lib/idna/__pycache__/idnadata.cpython-39.pyc b/lib/idna/__pycache__/idnadata.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6a87d38b48af08c2570b72777c0289fac27c7cf GIT binary patch literal 23178 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 literal 0 HcmV?d00001 diff --git a/lib/idna/__pycache__/intranges.cpython-39.pyc b/lib/idna/__pycache__/intranges.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ea8aa3b7daea7fe7d666cce32af3de7d26753c0 GIT binary patch literal 1974 zcmZuyO>fjj7@is1n`M)fP@p6whfz5+meMVxHxxcp5UnUmC7_2!TRDzrc4NhBo0(Z4 zXjfE9EA>ZkfMXB+L%#Nuzt9VP-f=$Y$5{5xyz_p|`#kS#y13Y57~lS}qJErX>`z*J zI6^G$;d0%(LvukbLf&oVtp|NheFTg1Y6&|%C7!VOLVAuf*83)Zu1lH9 zBDYRfLz$OOkF>Gta-1qHCp8Jz_NrPBCq;1(L)DJcqLAfeysHiE7F9WtW>VT77FktV zolP9ve&8#s#`VN$7#m$1ZFT8Vmscg3yFD$3l^OTe)~rj7yRj~HscyvUvR!ZZ^+qiF z{eIk+PxmO_(!M}&GJQ7D*1P)bMQY_sdF;NImwvl4`6w^b!XNO?XUIvqe5~d7Yeh9$ zyXNzb<9_c5n6Rx_G~pvuqKTeP>O%Lij;Jz8(=pmjOqro(r=x=zfM)mb+TZYTY|0M# zjE~q%C_W1ucVfy9@!V%W?asn!sKgc4%J!LgIOWrDh0&Mf09q`wePJFxTZJqjS-bNq zNHB}0;%(r|CL|_{F#GR&NE910kB439lD0JC$(z=F=W-&pL5He}^BJU~g z80zR|zBNpUC!mQ>v*{HnSo6ZvnU^2zMbsMDp znt^Cy>qtW8&l3wnvCNedftQ*~v?N1`*Lv67uhCrGv;hf&lR@(UuMfC{2?e}EUvZZW z>FNr$%rfK!X}y2_{&jF1pPLm(+Ne}u9~$5(eFok&$WM~^6muLuMCB55o(%qTdTMV% z*(dV~6KZkA55-J4{ua$W74O6>K(q4w&}_M&ZQNxv^C2IIS%|g`r*ILleQq91BeeZi zi95awRq%vOsiAQ_K>ED3J+5NzINJ-^Ae07${oskM-FitTe@p2 zSz+V&Ms0~f*WFG#-RXO#8#>X`2XW*fXga$&u4~uKx4%iDht5!44E>kIbLJ||sWwf= zvp#8py0T43is+SmwC5V0{JZ8C&d7*A1efOJ=Wx+dQS|si&;g`k0*|$lDWX97F^Gc<7=auIATH(r5-AK(3@MDk44O<;%*J{qewvK8xZ~r? zQj3Z+^Yh~4S27ea0abyCUw+P3F`>mkshHs8qRfIAm(=3ylKg_0^n#N3;*7+U)S{S@ z)Z!AO7@y3fnBvTmRNaEaTZlX-=vg$VH!l Gm;nIpfHEln literal 0 HcmV?d00001 diff --git a/lib/idna/__pycache__/uts46data.cpython-39.pyc b/lib/idna/__pycache__/uts46data.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5953ddf801b7b7ef164d9d81b8863ca9a4a3f754 GIT binary patch literal 153183 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 DgK5@@ literal 0 HcmV?d00001 diff --git a/lib/idna/codec.py b/lib/idna/codec.py new file mode 100644 index 0000000..1ca9ba6 --- /dev/null +++ b/lib/idna/codec.py @@ -0,0 +1,112 @@ +from .core import encode, decode, alabel, ulabel, IDNAError +import codecs +import re +from typing import Tuple, Optional + +_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') + +class Codec(codecs.Codec): + + def encode(self, data: str, errors: str = 'strict') -> Tuple[bytes, int]: + if errors != 'strict': + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) + + if not data: + return b"", 0 + + return encode(data), len(data) + + def decode(self, data: bytes, errors: str = 'strict') -> Tuple[str, int]: + if errors != 'strict': + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) + + if not data: + return '', 0 + + return decode(data), len(data) + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[str, int]: # type: ignore + if errors != 'strict': + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) + + if not data: + return "", 0 + + labels = _unicode_dots_re.split(data) + trailing_dot = '' + if labels: + if not labels[-1]: + trailing_dot = '.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = '.' + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result_str = '.'.join(result) + trailing_dot # type: ignore + size += len(trailing_dot) + return result_str, size + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + def _buffer_decode(self, data: str, errors: str, final: bool) -> Tuple[str, int]: # type: ignore + if errors != 'strict': + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) + + if not data: + return ('', 0) + + labels = _unicode_dots_re.split(data) + trailing_dot = '' + if labels: + if not labels[-1]: + trailing_dot = '.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = '.' + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result_str = '.'.join(result) + trailing_dot + size += len(trailing_dot) + return (result_str, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +def getregentry() -> codecs.CodecInfo: + # Compatibility as a search_function for codecs.register() + return codecs.CodecInfo( + name='idna', + encode=Codec().encode, # type: ignore + decode=Codec().decode, # type: ignore + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) diff --git a/lib/idna/compat.py b/lib/idna/compat.py new file mode 100644 index 0000000..786e6bd --- /dev/null +++ b/lib/idna/compat.py @@ -0,0 +1,13 @@ +from .core import * +from .codec import * +from typing import Any, Union + +def ToASCII(label: str) -> bytes: + return encode(label) + +def ToUnicode(label: Union[bytes, bytearray]) -> str: + return decode(label) + +def nameprep(s: Any) -> None: + raise NotImplementedError('IDNA 2008 does not utilise nameprep protocol') + diff --git a/lib/idna/core.py b/lib/idna/core.py new file mode 100644 index 0000000..4f30037 --- /dev/null +++ b/lib/idna/core.py @@ -0,0 +1,400 @@ +from . import idnadata +import bisect +import unicodedata +import re +from typing import Union, Optional +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b'xn--' +_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') + +class IDNAError(UnicodeError): + """ Base exception for all IDNA-encoding related problems """ + pass + + +class IDNABidiError(IDNAError): + """ Exception when bidirectional requirements are not satisfied """ + pass + + +class InvalidCodepoint(IDNAError): + """ Exception when a disallowed or unallocated codepoint is used """ + pass + + +class InvalidCodepointContext(IDNAError): + """ Exception when the codepoint is not valid in the context it is used """ + pass + + +def _combining_class(cp: int) -> int: + v = unicodedata.combining(chr(cp)) + if v == 0: + if not unicodedata.name(chr(cp)): + raise ValueError('Unknown character in unicodedata') + return v + +def _is_script(cp: str, script: str) -> bool: + return intranges_contain(ord(cp), idnadata.scripts[script]) + +def _punycode(s: str) -> bytes: + return s.encode('punycode') + +def _unot(s: int) -> str: + return 'U+{:04X}'.format(s) + + +def valid_label_length(label: Union[bytes, str]) -> bool: + if len(label) > 63: + return False + return True + + +def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool: + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label: str, check_ltr: bool = False) -> bool: + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == '': + # String likely comes from a newer version of Unicode + raise IDNABidiError('Unknown directionality in label {} at position {}'.format(repr(label), idx)) + if direction in ['R', 'AL', 'AN']: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ['R', 'AL']: + rtl = True + elif direction == 'L': + rtl = False + else: + raise IDNABidiError('First codepoint in label {} must be directionality L, R or AL'.format(repr(label))) + + valid_ending = False + number_type = None # type: Optional[str] + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {} in a right-to-left label'.format(idx)) + # Bidi rule 3 + if direction in ['R', 'AL', 'EN', 'AN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + # Bidi rule 4 + if direction in ['AN', 'EN']: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError('Can not mix numeral types in a right-to-left label') + else: + # Bidi rule 5 + if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {} in a left-to-right label'.format(idx)) + # Bidi rule 6 + if direction in ['L', 'EN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + + if not valid_ending: + raise IDNABidiError('Label ends with illegal codepoint directionality') + + return True + + +def check_initial_combiner(label: str) -> bool: + if unicodedata.category(label[0])[0] == 'M': + raise IDNAError('Label begins with an illegal combining character') + return True + + +def check_hyphen_ok(label: str) -> bool: + if label[2:4] == '--': + raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') + if label[0] == '-' or label[-1] == '-': + raise IDNAError('Label must not start or end with a hyphen') + return True + + +def check_nfc(label: str) -> None: + if unicodedata.normalize('NFC', label) != label: + raise IDNAError('Label must be in Normalization Form C') + + +def valid_contextj(label: str, pos: int) -> bool: + cp_value = ord(label[pos]) + + if cp_value == 0x200c: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos-1, -1, -1): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('L'), ord('D')]: + ok = True + break + + if not ok: + return False + + ok = False + for i in range(pos+1, len(label)): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('R'), ord('D')]: + ok = True + break + return ok + + if cp_value == 0x200d: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + + return False + + +def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: + cp_value = ord(label[pos]) + + if cp_value == 0x00b7: + if 0 < pos < len(label)-1: + if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label)-1 and len(label) > 1: + return _is_script(label[pos + 1], 'Greek') + return False + + elif cp_value == 0x05f3 or cp_value == 0x05f4: + if pos > 0: + return _is_script(label[pos - 1], 'Hebrew') + return False + + elif cp_value == 0x30fb: + for cp in label: + if cp == '\u30fb': + continue + if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6f0 <= ord(cp) <= 0x06f9: + return False + return True + + elif 0x6f0 <= cp_value <= 0x6f9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + return False + + +def check_label(label: Union[str, bytes, bytearray]) -> None: + if isinstance(label, (bytes, bytearray)): + label = label.decode('utf-8') + if len(label) == 0: + raise IDNAError('Empty Label') + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for (pos, cp) in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext('Joiner {} not allowed at position {} in {}'.format( + _unot(cp_value), pos+1, repr(label))) + except ValueError: + raise IDNAError('Unknown codepoint adjacent to joiner {} at position {} in {}'.format( + _unot(cp_value), pos+1, repr(label))) + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): + if not valid_contexto(label, pos): + raise InvalidCodepointContext('Codepoint {} not allowed at position {} in {}'.format(_unot(cp_value), pos+1, repr(label))) + else: + raise InvalidCodepoint('Codepoint {} at position {} of {} not allowed'.format(_unot(cp_value), pos+1, repr(label))) + + check_bidi(label) + + +def alabel(label: str) -> bytes: + try: + label_bytes = label.encode('ascii') + ulabel(label_bytes) + if not valid_label_length(label_bytes): + raise IDNAError('Label too long') + return label_bytes + except UnicodeEncodeError: + pass + + if not label: + raise IDNAError('No Input') + + label = str(label) + check_label(label) + label_bytes = _punycode(label) + label_bytes = _alabel_prefix + label_bytes + + if not valid_label_length(label_bytes): + raise IDNAError('Label too long') + + return label_bytes + + +def ulabel(label: Union[str, bytes, bytearray]) -> str: + if not isinstance(label, (bytes, bytearray)): + try: + label_bytes = label.encode('ascii') + except UnicodeEncodeError: + check_label(label) + return label + else: + label_bytes = label + + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix):] + if not label_bytes: + raise IDNAError('Malformed A-label, no Punycode eligible content found') + if label_bytes.decode('ascii')[-1] == '-': + raise IDNAError('A-label must not end with a hyphen') + else: + check_label(label_bytes) + return label_bytes.decode('ascii') + + try: + label = label_bytes.decode('punycode') + except UnicodeError: + raise IDNAError('Invalid A-label') + check_label(label) + return label + + +def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: + """Re-map the characters in the string according to UTS46 processing.""" + from .uts46data import uts46data + output = '' + + for pos, char in enumerate(domain): + code_point = ord(char) + try: + uts46row = uts46data[code_point if code_point < 256 else + bisect.bisect_left(uts46data, (code_point, 'Z')) - 1] + status = uts46row[1] + replacement = None # type: Optional[str] + if len(uts46row) == 3: + replacement = uts46row[2] # type: ignore + if (status == 'V' or + (status == 'D' and not transitional) or + (status == '3' and not std3_rules and replacement is None)): + output += char + elif replacement is not None and (status == 'M' or + (status == '3' and not std3_rules) or + (status == 'D' and transitional)): + output += replacement + elif status != 'I': + raise IndexError() + except IndexError: + raise InvalidCodepoint( + 'Codepoint {} not allowed at position {} in {}'.format( + _unot(code_point), pos + 1, repr(domain))) + + return unicodedata.normalize('NFC', output) + + +def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False, transitional: bool = False) -> bytes: + if isinstance(s, (bytes, bytearray)): + try: + s = s.decode('ascii') + except UnicodeDecodeError: + raise IDNAError('should pass a unicode string to the function rather than a byte string.') + if uts46: + s = uts46_remap(s, std3_rules, transitional) + trailing_dot = False + result = [] + if strict: + labels = s.split('.') + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if labels[-1] == '': + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(b'') + s = b'.'.join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError('Domain too long') + return s + + +def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False) -> str: + try: + if isinstance(s, (bytes, bytearray)): + s = s.decode('ascii') + except UnicodeDecodeError: + raise IDNAError('Invalid ASCII in A-label') + if uts46: + s = uts46_remap(s, std3_rules, False) + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split('.') + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append('') + return '.'.join(result) diff --git a/lib/idna/idnadata.py b/lib/idna/idnadata.py new file mode 100644 index 0000000..67db462 --- /dev/null +++ b/lib/idna/idnadata.py @@ -0,0 +1,2151 @@ +# This file is automatically generated by tools/idna-data + +__version__ = '15.0.0' +scripts = { + 'Greek': ( + 0x37000000374, + 0x37500000378, + 0x37a0000037e, + 0x37f00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038b, + 0x38c0000038d, + 0x38e000003a2, + 0x3a3000003e2, + 0x3f000000400, + 0x1d2600001d2b, + 0x1d5d00001d62, + 0x1d6600001d6b, + 0x1dbf00001dc0, + 0x1f0000001f16, + 0x1f1800001f1e, + 0x1f2000001f46, + 0x1f4800001f4e, + 0x1f5000001f58, + 0x1f5900001f5a, + 0x1f5b00001f5c, + 0x1f5d00001f5e, + 0x1f5f00001f7e, + 0x1f8000001fb5, + 0x1fb600001fc5, + 0x1fc600001fd4, + 0x1fd600001fdc, + 0x1fdd00001ff0, + 0x1ff200001ff5, + 0x1ff600001fff, + 0x212600002127, + 0xab650000ab66, + 0x101400001018f, + 0x101a0000101a1, + 0x1d2000001d246, + ), + 'Han': ( + 0x2e8000002e9a, + 0x2e9b00002ef4, + 0x2f0000002fd6, + 0x300500003006, + 0x300700003008, + 0x30210000302a, + 0x30380000303c, + 0x340000004dc0, + 0x4e000000a000, + 0xf9000000fa6e, + 0xfa700000fada, + 0x16fe200016fe4, + 0x16ff000016ff2, + 0x200000002a6e0, + 0x2a7000002b73a, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x2f8000002fa1e, + 0x300000003134b, + 0x31350000323b0, + ), + 'Hebrew': ( + 0x591000005c8, + 0x5d0000005eb, + 0x5ef000005f5, + 0xfb1d0000fb37, + 0xfb380000fb3d, + 0xfb3e0000fb3f, + 0xfb400000fb42, + 0xfb430000fb45, + 0xfb460000fb50, + ), + 'Hiragana': ( + 0x304100003097, + 0x309d000030a0, + 0x1b0010001b120, + 0x1b1320001b133, + 0x1b1500001b153, + 0x1f2000001f201, + ), + 'Katakana': ( + 0x30a1000030fb, + 0x30fd00003100, + 0x31f000003200, + 0x32d0000032ff, + 0x330000003358, + 0xff660000ff70, + 0xff710000ff9e, + 0x1aff00001aff4, + 0x1aff50001affc, + 0x1affd0001afff, + 0x1b0000001b001, + 0x1b1200001b123, + 0x1b1550001b156, + 0x1b1640001b168, + ), +} +joining_types = { + 0x600: 85, + 0x601: 85, + 0x602: 85, + 0x603: 85, + 0x604: 85, + 0x605: 85, + 0x608: 85, + 0x60b: 85, + 0x620: 68, + 0x621: 85, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62a: 68, + 0x62b: 68, + 0x62c: 68, + 0x62d: 68, + 0x62e: 68, + 0x62f: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63a: 68, + 0x63b: 68, + 0x63c: 68, + 0x63d: 68, + 0x63e: 68, + 0x63f: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64a: 68, + 0x66e: 68, + 0x66f: 68, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x674: 85, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67a: 68, + 0x67b: 68, + 0x67c: 68, + 0x67d: 68, + 0x67e: 68, + 0x67f: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68a: 82, + 0x68b: 82, + 0x68c: 82, + 0x68d: 82, + 0x68e: 82, + 0x68f: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69a: 68, + 0x69b: 68, + 0x69c: 68, + 0x69d: 68, + 0x69e: 68, + 0x69f: 68, + 0x6a0: 68, + 0x6a1: 68, + 0x6a2: 68, + 0x6a3: 68, + 0x6a4: 68, + 0x6a5: 68, + 0x6a6: 68, + 0x6a7: 68, + 0x6a8: 68, + 0x6a9: 68, + 0x6aa: 68, + 0x6ab: 68, + 0x6ac: 68, + 0x6ad: 68, + 0x6ae: 68, + 0x6af: 68, + 0x6b0: 68, + 0x6b1: 68, + 0x6b2: 68, + 0x6b3: 68, + 0x6b4: 68, + 0x6b5: 68, + 0x6b6: 68, + 0x6b7: 68, + 0x6b8: 68, + 0x6b9: 68, + 0x6ba: 68, + 0x6bb: 68, + 0x6bc: 68, + 0x6bd: 68, + 0x6be: 68, + 0x6bf: 68, + 0x6c0: 82, + 0x6c1: 68, + 0x6c2: 68, + 0x6c3: 82, + 0x6c4: 82, + 0x6c5: 82, + 0x6c6: 82, + 0x6c7: 82, + 0x6c8: 82, + 0x6c9: 82, + 0x6ca: 82, + 0x6cb: 82, + 0x6cc: 68, + 0x6cd: 82, + 0x6ce: 68, + 0x6cf: 82, + 0x6d0: 68, + 0x6d1: 68, + 0x6d2: 82, + 0x6d3: 82, + 0x6d5: 82, + 0x6dd: 85, + 0x6ee: 82, + 0x6ef: 82, + 0x6fa: 68, + 0x6fb: 68, + 0x6fc: 68, + 0x6ff: 68, + 0x70f: 84, + 0x710: 82, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71a: 68, + 0x71b: 68, + 0x71c: 68, + 0x71d: 68, + 0x71e: 82, + 0x71f: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72a: 82, + 0x72b: 68, + 0x72c: 82, + 0x72d: 68, + 0x72e: 68, + 0x72f: 82, + 0x74d: 82, + 0x74e: 68, + 0x74f: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75a: 82, + 0x75b: 82, + 0x75c: 68, + 0x75d: 68, + 0x75e: 68, + 0x75f: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76a: 68, + 0x76b: 82, + 0x76c: 82, + 0x76d: 68, + 0x76e: 68, + 0x76f: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77a: 68, + 0x77b: 68, + 0x77c: 68, + 0x77d: 68, + 0x77e: 68, + 0x77f: 68, + 0x7ca: 68, + 0x7cb: 68, + 0x7cc: 68, + 0x7cd: 68, + 0x7ce: 68, + 0x7cf: 68, + 0x7d0: 68, + 0x7d1: 68, + 0x7d2: 68, + 0x7d3: 68, + 0x7d4: 68, + 0x7d5: 68, + 0x7d6: 68, + 0x7d7: 68, + 0x7d8: 68, + 0x7d9: 68, + 0x7da: 68, + 0x7db: 68, + 0x7dc: 68, + 0x7dd: 68, + 0x7de: 68, + 0x7df: 68, + 0x7e0: 68, + 0x7e1: 68, + 0x7e2: 68, + 0x7e3: 68, + 0x7e4: 68, + 0x7e5: 68, + 0x7e6: 68, + 0x7e7: 68, + 0x7e8: 68, + 0x7e9: 68, + 0x7ea: 68, + 0x7fa: 67, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84a: 68, + 0x84b: 68, + 0x84c: 68, + 0x84d: 68, + 0x84e: 68, + 0x84f: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 82, + 0x857: 82, + 0x858: 82, + 0x860: 68, + 0x861: 85, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x866: 85, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86a: 82, + 0x870: 82, + 0x871: 82, + 0x872: 82, + 0x873: 82, + 0x874: 82, + 0x875: 82, + 0x876: 82, + 0x877: 82, + 0x878: 82, + 0x879: 82, + 0x87a: 82, + 0x87b: 82, + 0x87c: 82, + 0x87d: 82, + 0x87e: 82, + 0x87f: 82, + 0x880: 82, + 0x881: 82, + 0x882: 82, + 0x883: 67, + 0x884: 67, + 0x885: 67, + 0x886: 68, + 0x887: 85, + 0x888: 85, + 0x889: 68, + 0x88a: 68, + 0x88b: 68, + 0x88c: 68, + 0x88d: 68, + 0x88e: 82, + 0x890: 85, + 0x891: 85, + 0x8a0: 68, + 0x8a1: 68, + 0x8a2: 68, + 0x8a3: 68, + 0x8a4: 68, + 0x8a5: 68, + 0x8a6: 68, + 0x8a7: 68, + 0x8a8: 68, + 0x8a9: 68, + 0x8aa: 82, + 0x8ab: 82, + 0x8ac: 82, + 0x8ad: 85, + 0x8ae: 82, + 0x8af: 68, + 0x8b0: 68, + 0x8b1: 82, + 0x8b2: 82, + 0x8b3: 68, + 0x8b4: 68, + 0x8b5: 68, + 0x8b6: 68, + 0x8b7: 68, + 0x8b8: 68, + 0x8b9: 82, + 0x8ba: 68, + 0x8bb: 68, + 0x8bc: 68, + 0x8bd: 68, + 0x8be: 68, + 0x8bf: 68, + 0x8c0: 68, + 0x8c1: 68, + 0x8c2: 68, + 0x8c3: 68, + 0x8c4: 68, + 0x8c5: 68, + 0x8c6: 68, + 0x8c7: 68, + 0x8c8: 68, + 0x8e2: 85, + 0x1806: 85, + 0x1807: 68, + 0x180a: 67, + 0x180e: 85, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182a: 68, + 0x182b: 68, + 0x182c: 68, + 0x182d: 68, + 0x182e: 68, + 0x182f: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183a: 68, + 0x183b: 68, + 0x183c: 68, + 0x183d: 68, + 0x183e: 68, + 0x183f: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184a: 68, + 0x184b: 68, + 0x184c: 68, + 0x184d: 68, + 0x184e: 68, + 0x184f: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185a: 68, + 0x185b: 68, + 0x185c: 68, + 0x185d: 68, + 0x185e: 68, + 0x185f: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186a: 68, + 0x186b: 68, + 0x186c: 68, + 0x186d: 68, + 0x186e: 68, + 0x186f: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1880: 85, + 0x1881: 85, + 0x1882: 85, + 0x1883: 85, + 0x1884: 85, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188a: 68, + 0x188b: 68, + 0x188c: 68, + 0x188d: 68, + 0x188e: 68, + 0x188f: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189a: 68, + 0x189b: 68, + 0x189c: 68, + 0x189d: 68, + 0x189e: 68, + 0x189f: 68, + 0x18a0: 68, + 0x18a1: 68, + 0x18a2: 68, + 0x18a3: 68, + 0x18a4: 68, + 0x18a5: 68, + 0x18a6: 68, + 0x18a7: 68, + 0x18a8: 68, + 0x18aa: 68, + 0x200c: 85, + 0x200d: 67, + 0x202f: 85, + 0x2066: 85, + 0x2067: 85, + 0x2068: 85, + 0x2069: 85, + 0xa840: 68, + 0xa841: 68, + 0xa842: 68, + 0xa843: 68, + 0xa844: 68, + 0xa845: 68, + 0xa846: 68, + 0xa847: 68, + 0xa848: 68, + 0xa849: 68, + 0xa84a: 68, + 0xa84b: 68, + 0xa84c: 68, + 0xa84d: 68, + 0xa84e: 68, + 0xa84f: 68, + 0xa850: 68, + 0xa851: 68, + 0xa852: 68, + 0xa853: 68, + 0xa854: 68, + 0xa855: 68, + 0xa856: 68, + 0xa857: 68, + 0xa858: 68, + 0xa859: 68, + 0xa85a: 68, + 0xa85b: 68, + 0xa85c: 68, + 0xa85d: 68, + 0xa85e: 68, + 0xa85f: 68, + 0xa860: 68, + 0xa861: 68, + 0xa862: 68, + 0xa863: 68, + 0xa864: 68, + 0xa865: 68, + 0xa866: 68, + 0xa867: 68, + 0xa868: 68, + 0xa869: 68, + 0xa86a: 68, + 0xa86b: 68, + 0xa86c: 68, + 0xa86d: 68, + 0xa86e: 68, + 0xa86f: 68, + 0xa870: 68, + 0xa871: 68, + 0xa872: 76, + 0xa873: 85, + 0x10ac0: 68, + 0x10ac1: 68, + 0x10ac2: 68, + 0x10ac3: 68, + 0x10ac4: 68, + 0x10ac5: 82, + 0x10ac6: 85, + 0x10ac7: 82, + 0x10ac8: 85, + 0x10ac9: 82, + 0x10aca: 82, + 0x10acb: 85, + 0x10acc: 85, + 0x10acd: 76, + 0x10ace: 82, + 0x10acf: 82, + 0x10ad0: 82, + 0x10ad1: 82, + 0x10ad2: 82, + 0x10ad3: 68, + 0x10ad4: 68, + 0x10ad5: 68, + 0x10ad6: 68, + 0x10ad7: 76, + 0x10ad8: 68, + 0x10ad9: 68, + 0x10ada: 68, + 0x10adb: 68, + 0x10adc: 68, + 0x10add: 82, + 0x10ade: 68, + 0x10adf: 68, + 0x10ae0: 68, + 0x10ae1: 82, + 0x10ae2: 85, + 0x10ae3: 85, + 0x10ae4: 82, + 0x10aeb: 68, + 0x10aec: 68, + 0x10aed: 68, + 0x10aee: 68, + 0x10aef: 82, + 0x10b80: 68, + 0x10b81: 82, + 0x10b82: 68, + 0x10b83: 82, + 0x10b84: 82, + 0x10b85: 82, + 0x10b86: 68, + 0x10b87: 68, + 0x10b88: 68, + 0x10b89: 82, + 0x10b8a: 68, + 0x10b8b: 68, + 0x10b8c: 82, + 0x10b8d: 68, + 0x10b8e: 82, + 0x10b8f: 82, + 0x10b90: 68, + 0x10b91: 82, + 0x10ba9: 82, + 0x10baa: 82, + 0x10bab: 82, + 0x10bac: 82, + 0x10bad: 68, + 0x10bae: 68, + 0x10baf: 85, + 0x10d00: 76, + 0x10d01: 68, + 0x10d02: 68, + 0x10d03: 68, + 0x10d04: 68, + 0x10d05: 68, + 0x10d06: 68, + 0x10d07: 68, + 0x10d08: 68, + 0x10d09: 68, + 0x10d0a: 68, + 0x10d0b: 68, + 0x10d0c: 68, + 0x10d0d: 68, + 0x10d0e: 68, + 0x10d0f: 68, + 0x10d10: 68, + 0x10d11: 68, + 0x10d12: 68, + 0x10d13: 68, + 0x10d14: 68, + 0x10d15: 68, + 0x10d16: 68, + 0x10d17: 68, + 0x10d18: 68, + 0x10d19: 68, + 0x10d1a: 68, + 0x10d1b: 68, + 0x10d1c: 68, + 0x10d1d: 68, + 0x10d1e: 68, + 0x10d1f: 68, + 0x10d20: 68, + 0x10d21: 68, + 0x10d22: 82, + 0x10d23: 68, + 0x10f30: 68, + 0x10f31: 68, + 0x10f32: 68, + 0x10f33: 82, + 0x10f34: 68, + 0x10f35: 68, + 0x10f36: 68, + 0x10f37: 68, + 0x10f38: 68, + 0x10f39: 68, + 0x10f3a: 68, + 0x10f3b: 68, + 0x10f3c: 68, + 0x10f3d: 68, + 0x10f3e: 68, + 0x10f3f: 68, + 0x10f40: 68, + 0x10f41: 68, + 0x10f42: 68, + 0x10f43: 68, + 0x10f44: 68, + 0x10f45: 85, + 0x10f51: 68, + 0x10f52: 68, + 0x10f53: 68, + 0x10f54: 82, + 0x10f70: 68, + 0x10f71: 68, + 0x10f72: 68, + 0x10f73: 68, + 0x10f74: 82, + 0x10f75: 82, + 0x10f76: 68, + 0x10f77: 68, + 0x10f78: 68, + 0x10f79: 68, + 0x10f7a: 68, + 0x10f7b: 68, + 0x10f7c: 68, + 0x10f7d: 68, + 0x10f7e: 68, + 0x10f7f: 68, + 0x10f80: 68, + 0x10f81: 68, + 0x10fb0: 68, + 0x10fb1: 85, + 0x10fb2: 68, + 0x10fb3: 68, + 0x10fb4: 82, + 0x10fb5: 82, + 0x10fb6: 82, + 0x10fb7: 85, + 0x10fb8: 68, + 0x10fb9: 82, + 0x10fba: 82, + 0x10fbb: 68, + 0x10fbc: 68, + 0x10fbd: 82, + 0x10fbe: 68, + 0x10fbf: 68, + 0x10fc0: 85, + 0x10fc1: 68, + 0x10fc2: 82, + 0x10fc3: 82, + 0x10fc4: 68, + 0x10fc5: 85, + 0x10fc6: 85, + 0x10fc7: 85, + 0x10fc8: 85, + 0x10fc9: 82, + 0x10fca: 68, + 0x10fcb: 76, + 0x110bd: 85, + 0x110cd: 85, + 0x1e900: 68, + 0x1e901: 68, + 0x1e902: 68, + 0x1e903: 68, + 0x1e904: 68, + 0x1e905: 68, + 0x1e906: 68, + 0x1e907: 68, + 0x1e908: 68, + 0x1e909: 68, + 0x1e90a: 68, + 0x1e90b: 68, + 0x1e90c: 68, + 0x1e90d: 68, + 0x1e90e: 68, + 0x1e90f: 68, + 0x1e910: 68, + 0x1e911: 68, + 0x1e912: 68, + 0x1e913: 68, + 0x1e914: 68, + 0x1e915: 68, + 0x1e916: 68, + 0x1e917: 68, + 0x1e918: 68, + 0x1e919: 68, + 0x1e91a: 68, + 0x1e91b: 68, + 0x1e91c: 68, + 0x1e91d: 68, + 0x1e91e: 68, + 0x1e91f: 68, + 0x1e920: 68, + 0x1e921: 68, + 0x1e922: 68, + 0x1e923: 68, + 0x1e924: 68, + 0x1e925: 68, + 0x1e926: 68, + 0x1e927: 68, + 0x1e928: 68, + 0x1e929: 68, + 0x1e92a: 68, + 0x1e92b: 68, + 0x1e92c: 68, + 0x1e92d: 68, + 0x1e92e: 68, + 0x1e92f: 68, + 0x1e930: 68, + 0x1e931: 68, + 0x1e932: 68, + 0x1e933: 68, + 0x1e934: 68, + 0x1e935: 68, + 0x1e936: 68, + 0x1e937: 68, + 0x1e938: 68, + 0x1e939: 68, + 0x1e93a: 68, + 0x1e93b: 68, + 0x1e93c: 68, + 0x1e93d: 68, + 0x1e93e: 68, + 0x1e93f: 68, + 0x1e940: 68, + 0x1e941: 68, + 0x1e942: 68, + 0x1e943: 68, + 0x1e94b: 84, +} +codepoint_classes = { + 'PVALID': ( + 0x2d0000002e, + 0x300000003a, + 0x610000007b, + 0xdf000000f7, + 0xf800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010a, + 0x10b0000010c, + 0x10d0000010e, + 0x10f00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011a, + 0x11b0000011c, + 0x11d0000011e, + 0x11f00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012a, + 0x12b0000012c, + 0x12d0000012e, + 0x12f00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13a0000013b, + 0x13c0000013d, + 0x13e0000013f, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14b0000014c, + 0x14d0000014e, + 0x14f00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015a, + 0x15b0000015c, + 0x15d0000015e, + 0x15f00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016a, + 0x16b0000016c, + 0x16d0000016e, + 0x16f00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17a0000017b, + 0x17c0000017d, + 0x17e0000017f, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18c0000018e, + 0x19200000193, + 0x19500000196, + 0x1990000019c, + 0x19e0000019f, + 0x1a1000001a2, + 0x1a3000001a4, + 0x1a5000001a6, + 0x1a8000001a9, + 0x1aa000001ac, + 0x1ad000001ae, + 0x1b0000001b1, + 0x1b4000001b5, + 0x1b6000001b7, + 0x1b9000001bc, + 0x1bd000001c4, + 0x1ce000001cf, + 0x1d0000001d1, + 0x1d2000001d3, + 0x1d4000001d5, + 0x1d6000001d7, + 0x1d8000001d9, + 0x1da000001db, + 0x1dc000001de, + 0x1df000001e0, + 0x1e1000001e2, + 0x1e3000001e4, + 0x1e5000001e6, + 0x1e7000001e8, + 0x1e9000001ea, + 0x1eb000001ec, + 0x1ed000001ee, + 0x1ef000001f1, + 0x1f5000001f6, + 0x1f9000001fa, + 0x1fb000001fc, + 0x1fd000001fe, + 0x1ff00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020a, + 0x20b0000020c, + 0x20d0000020e, + 0x20f00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021a, + 0x21b0000021c, + 0x21d0000021e, + 0x21f00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022a, + 0x22b0000022c, + 0x22d0000022e, + 0x22f00000230, + 0x23100000232, + 0x2330000023a, + 0x23c0000023d, + 0x23f00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024a, + 0x24b0000024c, + 0x24d0000024e, + 0x24f000002b0, + 0x2b9000002c2, + 0x2c6000002d2, + 0x2ec000002ed, + 0x2ee000002ef, + 0x30000000340, + 0x34200000343, + 0x3460000034f, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37b0000037e, + 0x39000000391, + 0x3ac000003cf, + 0x3d7000003d8, + 0x3d9000003da, + 0x3db000003dc, + 0x3dd000003de, + 0x3df000003e0, + 0x3e1000003e2, + 0x3e3000003e4, + 0x3e5000003e6, + 0x3e7000003e8, + 0x3e9000003ea, + 0x3eb000003ec, + 0x3ed000003ee, + 0x3ef000003f0, + 0x3f3000003f4, + 0x3f8000003f9, + 0x3fb000003fd, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046a, + 0x46b0000046c, + 0x46d0000046e, + 0x46f00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047a, + 0x47b0000047c, + 0x47d0000047e, + 0x47f00000480, + 0x48100000482, + 0x48300000488, + 0x48b0000048c, + 0x48d0000048e, + 0x48f00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049a, + 0x49b0000049c, + 0x49d0000049e, + 0x49f000004a0, + 0x4a1000004a2, + 0x4a3000004a4, + 0x4a5000004a6, + 0x4a7000004a8, + 0x4a9000004aa, + 0x4ab000004ac, + 0x4ad000004ae, + 0x4af000004b0, + 0x4b1000004b2, + 0x4b3000004b4, + 0x4b5000004b6, + 0x4b7000004b8, + 0x4b9000004ba, + 0x4bb000004bc, + 0x4bd000004be, + 0x4bf000004c0, + 0x4c2000004c3, + 0x4c4000004c5, + 0x4c6000004c7, + 0x4c8000004c9, + 0x4ca000004cb, + 0x4cc000004cd, + 0x4ce000004d0, + 0x4d1000004d2, + 0x4d3000004d4, + 0x4d5000004d6, + 0x4d7000004d8, + 0x4d9000004da, + 0x4db000004dc, + 0x4dd000004de, + 0x4df000004e0, + 0x4e1000004e2, + 0x4e3000004e4, + 0x4e5000004e6, + 0x4e7000004e8, + 0x4e9000004ea, + 0x4eb000004ec, + 0x4ed000004ee, + 0x4ef000004f0, + 0x4f1000004f2, + 0x4f3000004f4, + 0x4f5000004f6, + 0x4f7000004f8, + 0x4f9000004fa, + 0x4fb000004fc, + 0x4fd000004fe, + 0x4ff00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050a, + 0x50b0000050c, + 0x50d0000050e, + 0x50f00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051a, + 0x51b0000051c, + 0x51d0000051e, + 0x51f00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052a, + 0x52b0000052c, + 0x52d0000052e, + 0x52f00000530, + 0x5590000055a, + 0x56000000587, + 0x58800000589, + 0x591000005be, + 0x5bf000005c0, + 0x5c1000005c3, + 0x5c4000005c6, + 0x5c7000005c8, + 0x5d0000005eb, + 0x5ef000005f3, + 0x6100000061b, + 0x62000000640, + 0x64100000660, + 0x66e00000675, + 0x679000006d4, + 0x6d5000006dd, + 0x6df000006e9, + 0x6ea000006f0, + 0x6fa00000700, + 0x7100000074b, + 0x74d000007b2, + 0x7c0000007f6, + 0x7fd000007fe, + 0x8000000082e, + 0x8400000085c, + 0x8600000086b, + 0x87000000888, + 0x8890000088f, + 0x898000008e2, + 0x8e300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098d, + 0x98f00000991, + 0x993000009a9, + 0x9aa000009b1, + 0x9b2000009b3, + 0x9b6000009ba, + 0x9bc000009c5, + 0x9c7000009c9, + 0x9cb000009cf, + 0x9d7000009d8, + 0x9e0000009e4, + 0x9e6000009f2, + 0x9fc000009fd, + 0x9fe000009ff, + 0xa0100000a04, + 0xa0500000a0b, + 0xa0f00000a11, + 0xa1300000a29, + 0xa2a00000a31, + 0xa3200000a33, + 0xa3500000a36, + 0xa3800000a3a, + 0xa3c00000a3d, + 0xa3e00000a43, + 0xa4700000a49, + 0xa4b00000a4e, + 0xa5100000a52, + 0xa5c00000a5d, + 0xa6600000a76, + 0xa8100000a84, + 0xa8500000a8e, + 0xa8f00000a92, + 0xa9300000aa9, + 0xaaa00000ab1, + 0xab200000ab4, + 0xab500000aba, + 0xabc00000ac6, + 0xac700000aca, + 0xacb00000ace, + 0xad000000ad1, + 0xae000000ae4, + 0xae600000af0, + 0xaf900000b00, + 0xb0100000b04, + 0xb0500000b0d, + 0xb0f00000b11, + 0xb1300000b29, + 0xb2a00000b31, + 0xb3200000b34, + 0xb3500000b3a, + 0xb3c00000b45, + 0xb4700000b49, + 0xb4b00000b4e, + 0xb5500000b58, + 0xb5f00000b64, + 0xb6600000b70, + 0xb7100000b72, + 0xb8200000b84, + 0xb8500000b8b, + 0xb8e00000b91, + 0xb9200000b96, + 0xb9900000b9b, + 0xb9c00000b9d, + 0xb9e00000ba0, + 0xba300000ba5, + 0xba800000bab, + 0xbae00000bba, + 0xbbe00000bc3, + 0xbc600000bc9, + 0xbca00000bce, + 0xbd000000bd1, + 0xbd700000bd8, + 0xbe600000bf0, + 0xc0000000c0d, + 0xc0e00000c11, + 0xc1200000c29, + 0xc2a00000c3a, + 0xc3c00000c45, + 0xc4600000c49, + 0xc4a00000c4e, + 0xc5500000c57, + 0xc5800000c5b, + 0xc5d00000c5e, + 0xc6000000c64, + 0xc6600000c70, + 0xc8000000c84, + 0xc8500000c8d, + 0xc8e00000c91, + 0xc9200000ca9, + 0xcaa00000cb4, + 0xcb500000cba, + 0xcbc00000cc5, + 0xcc600000cc9, + 0xcca00000cce, + 0xcd500000cd7, + 0xcdd00000cdf, + 0xce000000ce4, + 0xce600000cf0, + 0xcf100000cf4, + 0xd0000000d0d, + 0xd0e00000d11, + 0xd1200000d45, + 0xd4600000d49, + 0xd4a00000d4f, + 0xd5400000d58, + 0xd5f00000d64, + 0xd6600000d70, + 0xd7a00000d80, + 0xd8100000d84, + 0xd8500000d97, + 0xd9a00000db2, + 0xdb300000dbc, + 0xdbd00000dbe, + 0xdc000000dc7, + 0xdca00000dcb, + 0xdcf00000dd5, + 0xdd600000dd7, + 0xdd800000de0, + 0xde600000df0, + 0xdf200000df4, + 0xe0100000e33, + 0xe3400000e3b, + 0xe4000000e4f, + 0xe5000000e5a, + 0xe8100000e83, + 0xe8400000e85, + 0xe8600000e8b, + 0xe8c00000ea4, + 0xea500000ea6, + 0xea700000eb3, + 0xeb400000ebe, + 0xec000000ec5, + 0xec600000ec7, + 0xec800000ecf, + 0xed000000eda, + 0xede00000ee0, + 0xf0000000f01, + 0xf0b00000f0c, + 0xf1800000f1a, + 0xf2000000f2a, + 0xf3500000f36, + 0xf3700000f38, + 0xf3900000f3a, + 0xf3e00000f43, + 0xf4400000f48, + 0xf4900000f4d, + 0xf4e00000f52, + 0xf5300000f57, + 0xf5800000f5c, + 0xf5d00000f69, + 0xf6a00000f6d, + 0xf7100000f73, + 0xf7400000f75, + 0xf7a00000f81, + 0xf8200000f85, + 0xf8600000f93, + 0xf9400000f98, + 0xf9900000f9d, + 0xf9e00000fa2, + 0xfa300000fa7, + 0xfa800000fac, + 0xfad00000fb9, + 0xfba00000fbd, + 0xfc600000fc7, + 0x10000000104a, + 0x10500000109e, + 0x10d0000010fb, + 0x10fd00001100, + 0x120000001249, + 0x124a0000124e, + 0x125000001257, + 0x125800001259, + 0x125a0000125e, + 0x126000001289, + 0x128a0000128e, + 0x1290000012b1, + 0x12b2000012b6, + 0x12b8000012bf, + 0x12c0000012c1, + 0x12c2000012c6, + 0x12c8000012d7, + 0x12d800001311, + 0x131200001316, + 0x13180000135b, + 0x135d00001360, + 0x138000001390, + 0x13a0000013f6, + 0x14010000166d, + 0x166f00001680, + 0x16810000169b, + 0x16a0000016eb, + 0x16f1000016f9, + 0x170000001716, + 0x171f00001735, + 0x174000001754, + 0x17600000176d, + 0x176e00001771, + 0x177200001774, + 0x1780000017b4, + 0x17b6000017d4, + 0x17d7000017d8, + 0x17dc000017de, + 0x17e0000017ea, + 0x18100000181a, + 0x182000001879, + 0x1880000018ab, + 0x18b0000018f6, + 0x19000000191f, + 0x19200000192c, + 0x19300000193c, + 0x19460000196e, + 0x197000001975, + 0x1980000019ac, + 0x19b0000019ca, + 0x19d0000019da, + 0x1a0000001a1c, + 0x1a2000001a5f, + 0x1a6000001a7d, + 0x1a7f00001a8a, + 0x1a9000001a9a, + 0x1aa700001aa8, + 0x1ab000001abe, + 0x1abf00001acf, + 0x1b0000001b4d, + 0x1b5000001b5a, + 0x1b6b00001b74, + 0x1b8000001bf4, + 0x1c0000001c38, + 0x1c4000001c4a, + 0x1c4d00001c7e, + 0x1cd000001cd3, + 0x1cd400001cfb, + 0x1d0000001d2c, + 0x1d2f00001d30, + 0x1d3b00001d3c, + 0x1d4e00001d4f, + 0x1d6b00001d78, + 0x1d7900001d9b, + 0x1dc000001e00, + 0x1e0100001e02, + 0x1e0300001e04, + 0x1e0500001e06, + 0x1e0700001e08, + 0x1e0900001e0a, + 0x1e0b00001e0c, + 0x1e0d00001e0e, + 0x1e0f00001e10, + 0x1e1100001e12, + 0x1e1300001e14, + 0x1e1500001e16, + 0x1e1700001e18, + 0x1e1900001e1a, + 0x1e1b00001e1c, + 0x1e1d00001e1e, + 0x1e1f00001e20, + 0x1e2100001e22, + 0x1e2300001e24, + 0x1e2500001e26, + 0x1e2700001e28, + 0x1e2900001e2a, + 0x1e2b00001e2c, + 0x1e2d00001e2e, + 0x1e2f00001e30, + 0x1e3100001e32, + 0x1e3300001e34, + 0x1e3500001e36, + 0x1e3700001e38, + 0x1e3900001e3a, + 0x1e3b00001e3c, + 0x1e3d00001e3e, + 0x1e3f00001e40, + 0x1e4100001e42, + 0x1e4300001e44, + 0x1e4500001e46, + 0x1e4700001e48, + 0x1e4900001e4a, + 0x1e4b00001e4c, + 0x1e4d00001e4e, + 0x1e4f00001e50, + 0x1e5100001e52, + 0x1e5300001e54, + 0x1e5500001e56, + 0x1e5700001e58, + 0x1e5900001e5a, + 0x1e5b00001e5c, + 0x1e5d00001e5e, + 0x1e5f00001e60, + 0x1e6100001e62, + 0x1e6300001e64, + 0x1e6500001e66, + 0x1e6700001e68, + 0x1e6900001e6a, + 0x1e6b00001e6c, + 0x1e6d00001e6e, + 0x1e6f00001e70, + 0x1e7100001e72, + 0x1e7300001e74, + 0x1e7500001e76, + 0x1e7700001e78, + 0x1e7900001e7a, + 0x1e7b00001e7c, + 0x1e7d00001e7e, + 0x1e7f00001e80, + 0x1e8100001e82, + 0x1e8300001e84, + 0x1e8500001e86, + 0x1e8700001e88, + 0x1e8900001e8a, + 0x1e8b00001e8c, + 0x1e8d00001e8e, + 0x1e8f00001e90, + 0x1e9100001e92, + 0x1e9300001e94, + 0x1e9500001e9a, + 0x1e9c00001e9e, + 0x1e9f00001ea0, + 0x1ea100001ea2, + 0x1ea300001ea4, + 0x1ea500001ea6, + 0x1ea700001ea8, + 0x1ea900001eaa, + 0x1eab00001eac, + 0x1ead00001eae, + 0x1eaf00001eb0, + 0x1eb100001eb2, + 0x1eb300001eb4, + 0x1eb500001eb6, + 0x1eb700001eb8, + 0x1eb900001eba, + 0x1ebb00001ebc, + 0x1ebd00001ebe, + 0x1ebf00001ec0, + 0x1ec100001ec2, + 0x1ec300001ec4, + 0x1ec500001ec6, + 0x1ec700001ec8, + 0x1ec900001eca, + 0x1ecb00001ecc, + 0x1ecd00001ece, + 0x1ecf00001ed0, + 0x1ed100001ed2, + 0x1ed300001ed4, + 0x1ed500001ed6, + 0x1ed700001ed8, + 0x1ed900001eda, + 0x1edb00001edc, + 0x1edd00001ede, + 0x1edf00001ee0, + 0x1ee100001ee2, + 0x1ee300001ee4, + 0x1ee500001ee6, + 0x1ee700001ee8, + 0x1ee900001eea, + 0x1eeb00001eec, + 0x1eed00001eee, + 0x1eef00001ef0, + 0x1ef100001ef2, + 0x1ef300001ef4, + 0x1ef500001ef6, + 0x1ef700001ef8, + 0x1ef900001efa, + 0x1efb00001efc, + 0x1efd00001efe, + 0x1eff00001f08, + 0x1f1000001f16, + 0x1f2000001f28, + 0x1f3000001f38, + 0x1f4000001f46, + 0x1f5000001f58, + 0x1f6000001f68, + 0x1f7000001f71, + 0x1f7200001f73, + 0x1f7400001f75, + 0x1f7600001f77, + 0x1f7800001f79, + 0x1f7a00001f7b, + 0x1f7c00001f7d, + 0x1fb000001fb2, + 0x1fb600001fb7, + 0x1fc600001fc7, + 0x1fd000001fd3, + 0x1fd600001fd8, + 0x1fe000001fe3, + 0x1fe400001fe8, + 0x1ff600001ff7, + 0x214e0000214f, + 0x218400002185, + 0x2c3000002c60, + 0x2c6100002c62, + 0x2c6500002c67, + 0x2c6800002c69, + 0x2c6a00002c6b, + 0x2c6c00002c6d, + 0x2c7100002c72, + 0x2c7300002c75, + 0x2c7600002c7c, + 0x2c8100002c82, + 0x2c8300002c84, + 0x2c8500002c86, + 0x2c8700002c88, + 0x2c8900002c8a, + 0x2c8b00002c8c, + 0x2c8d00002c8e, + 0x2c8f00002c90, + 0x2c9100002c92, + 0x2c9300002c94, + 0x2c9500002c96, + 0x2c9700002c98, + 0x2c9900002c9a, + 0x2c9b00002c9c, + 0x2c9d00002c9e, + 0x2c9f00002ca0, + 0x2ca100002ca2, + 0x2ca300002ca4, + 0x2ca500002ca6, + 0x2ca700002ca8, + 0x2ca900002caa, + 0x2cab00002cac, + 0x2cad00002cae, + 0x2caf00002cb0, + 0x2cb100002cb2, + 0x2cb300002cb4, + 0x2cb500002cb6, + 0x2cb700002cb8, + 0x2cb900002cba, + 0x2cbb00002cbc, + 0x2cbd00002cbe, + 0x2cbf00002cc0, + 0x2cc100002cc2, + 0x2cc300002cc4, + 0x2cc500002cc6, + 0x2cc700002cc8, + 0x2cc900002cca, + 0x2ccb00002ccc, + 0x2ccd00002cce, + 0x2ccf00002cd0, + 0x2cd100002cd2, + 0x2cd300002cd4, + 0x2cd500002cd6, + 0x2cd700002cd8, + 0x2cd900002cda, + 0x2cdb00002cdc, + 0x2cdd00002cde, + 0x2cdf00002ce0, + 0x2ce100002ce2, + 0x2ce300002ce5, + 0x2cec00002ced, + 0x2cee00002cf2, + 0x2cf300002cf4, + 0x2d0000002d26, + 0x2d2700002d28, + 0x2d2d00002d2e, + 0x2d3000002d68, + 0x2d7f00002d97, + 0x2da000002da7, + 0x2da800002daf, + 0x2db000002db7, + 0x2db800002dbf, + 0x2dc000002dc7, + 0x2dc800002dcf, + 0x2dd000002dd7, + 0x2dd800002ddf, + 0x2de000002e00, + 0x2e2f00002e30, + 0x300500003008, + 0x302a0000302e, + 0x303c0000303d, + 0x304100003097, + 0x30990000309b, + 0x309d0000309f, + 0x30a1000030fb, + 0x30fc000030ff, + 0x310500003130, + 0x31a0000031c0, + 0x31f000003200, + 0x340000004dc0, + 0x4e000000a48d, + 0xa4d00000a4fe, + 0xa5000000a60d, + 0xa6100000a62c, + 0xa6410000a642, + 0xa6430000a644, + 0xa6450000a646, + 0xa6470000a648, + 0xa6490000a64a, + 0xa64b0000a64c, + 0xa64d0000a64e, + 0xa64f0000a650, + 0xa6510000a652, + 0xa6530000a654, + 0xa6550000a656, + 0xa6570000a658, + 0xa6590000a65a, + 0xa65b0000a65c, + 0xa65d0000a65e, + 0xa65f0000a660, + 0xa6610000a662, + 0xa6630000a664, + 0xa6650000a666, + 0xa6670000a668, + 0xa6690000a66a, + 0xa66b0000a66c, + 0xa66d0000a670, + 0xa6740000a67e, + 0xa67f0000a680, + 0xa6810000a682, + 0xa6830000a684, + 0xa6850000a686, + 0xa6870000a688, + 0xa6890000a68a, + 0xa68b0000a68c, + 0xa68d0000a68e, + 0xa68f0000a690, + 0xa6910000a692, + 0xa6930000a694, + 0xa6950000a696, + 0xa6970000a698, + 0xa6990000a69a, + 0xa69b0000a69c, + 0xa69e0000a6e6, + 0xa6f00000a6f2, + 0xa7170000a720, + 0xa7230000a724, + 0xa7250000a726, + 0xa7270000a728, + 0xa7290000a72a, + 0xa72b0000a72c, + 0xa72d0000a72e, + 0xa72f0000a732, + 0xa7330000a734, + 0xa7350000a736, + 0xa7370000a738, + 0xa7390000a73a, + 0xa73b0000a73c, + 0xa73d0000a73e, + 0xa73f0000a740, + 0xa7410000a742, + 0xa7430000a744, + 0xa7450000a746, + 0xa7470000a748, + 0xa7490000a74a, + 0xa74b0000a74c, + 0xa74d0000a74e, + 0xa74f0000a750, + 0xa7510000a752, + 0xa7530000a754, + 0xa7550000a756, + 0xa7570000a758, + 0xa7590000a75a, + 0xa75b0000a75c, + 0xa75d0000a75e, + 0xa75f0000a760, + 0xa7610000a762, + 0xa7630000a764, + 0xa7650000a766, + 0xa7670000a768, + 0xa7690000a76a, + 0xa76b0000a76c, + 0xa76d0000a76e, + 0xa76f0000a770, + 0xa7710000a779, + 0xa77a0000a77b, + 0xa77c0000a77d, + 0xa77f0000a780, + 0xa7810000a782, + 0xa7830000a784, + 0xa7850000a786, + 0xa7870000a789, + 0xa78c0000a78d, + 0xa78e0000a790, + 0xa7910000a792, + 0xa7930000a796, + 0xa7970000a798, + 0xa7990000a79a, + 0xa79b0000a79c, + 0xa79d0000a79e, + 0xa79f0000a7a0, + 0xa7a10000a7a2, + 0xa7a30000a7a4, + 0xa7a50000a7a6, + 0xa7a70000a7a8, + 0xa7a90000a7aa, + 0xa7af0000a7b0, + 0xa7b50000a7b6, + 0xa7b70000a7b8, + 0xa7b90000a7ba, + 0xa7bb0000a7bc, + 0xa7bd0000a7be, + 0xa7bf0000a7c0, + 0xa7c10000a7c2, + 0xa7c30000a7c4, + 0xa7c80000a7c9, + 0xa7ca0000a7cb, + 0xa7d10000a7d2, + 0xa7d30000a7d4, + 0xa7d50000a7d6, + 0xa7d70000a7d8, + 0xa7d90000a7da, + 0xa7f20000a7f5, + 0xa7f60000a7f8, + 0xa7fa0000a828, + 0xa82c0000a82d, + 0xa8400000a874, + 0xa8800000a8c6, + 0xa8d00000a8da, + 0xa8e00000a8f8, + 0xa8fb0000a8fc, + 0xa8fd0000a92e, + 0xa9300000a954, + 0xa9800000a9c1, + 0xa9cf0000a9da, + 0xa9e00000a9ff, + 0xaa000000aa37, + 0xaa400000aa4e, + 0xaa500000aa5a, + 0xaa600000aa77, + 0xaa7a0000aac3, + 0xaadb0000aade, + 0xaae00000aaf0, + 0xaaf20000aaf7, + 0xab010000ab07, + 0xab090000ab0f, + 0xab110000ab17, + 0xab200000ab27, + 0xab280000ab2f, + 0xab300000ab5b, + 0xab600000ab69, + 0xabc00000abeb, + 0xabec0000abee, + 0xabf00000abfa, + 0xac000000d7a4, + 0xfa0e0000fa10, + 0xfa110000fa12, + 0xfa130000fa15, + 0xfa1f0000fa20, + 0xfa210000fa22, + 0xfa230000fa25, + 0xfa270000fa2a, + 0xfb1e0000fb1f, + 0xfe200000fe30, + 0xfe730000fe74, + 0x100000001000c, + 0x1000d00010027, + 0x100280001003b, + 0x1003c0001003e, + 0x1003f0001004e, + 0x100500001005e, + 0x10080000100fb, + 0x101fd000101fe, + 0x102800001029d, + 0x102a0000102d1, + 0x102e0000102e1, + 0x1030000010320, + 0x1032d00010341, + 0x103420001034a, + 0x103500001037b, + 0x103800001039e, + 0x103a0000103c4, + 0x103c8000103d0, + 0x104280001049e, + 0x104a0000104aa, + 0x104d8000104fc, + 0x1050000010528, + 0x1053000010564, + 0x10597000105a2, + 0x105a3000105b2, + 0x105b3000105ba, + 0x105bb000105bd, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1078000010786, + 0x10787000107b1, + 0x107b2000107bb, + 0x1080000010806, + 0x1080800010809, + 0x1080a00010836, + 0x1083700010839, + 0x1083c0001083d, + 0x1083f00010856, + 0x1086000010877, + 0x108800001089f, + 0x108e0000108f3, + 0x108f4000108f6, + 0x1090000010916, + 0x109200001093a, + 0x10980000109b8, + 0x109be000109c0, + 0x10a0000010a04, + 0x10a0500010a07, + 0x10a0c00010a14, + 0x10a1500010a18, + 0x10a1900010a36, + 0x10a3800010a3b, + 0x10a3f00010a40, + 0x10a6000010a7d, + 0x10a8000010a9d, + 0x10ac000010ac8, + 0x10ac900010ae7, + 0x10b0000010b36, + 0x10b4000010b56, + 0x10b6000010b73, + 0x10b8000010b92, + 0x10c0000010c49, + 0x10cc000010cf3, + 0x10d0000010d28, + 0x10d3000010d3a, + 0x10e8000010eaa, + 0x10eab00010ead, + 0x10eb000010eb2, + 0x10efd00010f1d, + 0x10f2700010f28, + 0x10f3000010f51, + 0x10f7000010f86, + 0x10fb000010fc5, + 0x10fe000010ff7, + 0x1100000011047, + 0x1106600011076, + 0x1107f000110bb, + 0x110c2000110c3, + 0x110d0000110e9, + 0x110f0000110fa, + 0x1110000011135, + 0x1113600011140, + 0x1114400011148, + 0x1115000011174, + 0x1117600011177, + 0x11180000111c5, + 0x111c9000111cd, + 0x111ce000111db, + 0x111dc000111dd, + 0x1120000011212, + 0x1121300011238, + 0x1123e00011242, + 0x1128000011287, + 0x1128800011289, + 0x1128a0001128e, + 0x1128f0001129e, + 0x1129f000112a9, + 0x112b0000112eb, + 0x112f0000112fa, + 0x1130000011304, + 0x113050001130d, + 0x1130f00011311, + 0x1131300011329, + 0x1132a00011331, + 0x1133200011334, + 0x113350001133a, + 0x1133b00011345, + 0x1134700011349, + 0x1134b0001134e, + 0x1135000011351, + 0x1135700011358, + 0x1135d00011364, + 0x113660001136d, + 0x1137000011375, + 0x114000001144b, + 0x114500001145a, + 0x1145e00011462, + 0x11480000114c6, + 0x114c7000114c8, + 0x114d0000114da, + 0x11580000115b6, + 0x115b8000115c1, + 0x115d8000115de, + 0x1160000011641, + 0x1164400011645, + 0x116500001165a, + 0x11680000116b9, + 0x116c0000116ca, + 0x117000001171b, + 0x1171d0001172c, + 0x117300001173a, + 0x1174000011747, + 0x118000001183b, + 0x118c0000118ea, + 0x118ff00011907, + 0x119090001190a, + 0x1190c00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193b00011944, + 0x119500001195a, + 0x119a0000119a8, + 0x119aa000119d8, + 0x119da000119e2, + 0x119e3000119e5, + 0x11a0000011a3f, + 0x11a4700011a48, + 0x11a5000011a9a, + 0x11a9d00011a9e, + 0x11ab000011af9, + 0x11c0000011c09, + 0x11c0a00011c37, + 0x11c3800011c41, + 0x11c5000011c5a, + 0x11c7200011c90, + 0x11c9200011ca8, + 0x11ca900011cb7, + 0x11d0000011d07, + 0x11d0800011d0a, + 0x11d0b00011d37, + 0x11d3a00011d3b, + 0x11d3c00011d3e, + 0x11d3f00011d48, + 0x11d5000011d5a, + 0x11d6000011d66, + 0x11d6700011d69, + 0x11d6a00011d8f, + 0x11d9000011d92, + 0x11d9300011d99, + 0x11da000011daa, + 0x11ee000011ef7, + 0x11f0000011f11, + 0x11f1200011f3b, + 0x11f3e00011f43, + 0x11f5000011f5a, + 0x11fb000011fb1, + 0x120000001239a, + 0x1248000012544, + 0x12f9000012ff1, + 0x1300000013430, + 0x1344000013456, + 0x1440000014647, + 0x1680000016a39, + 0x16a4000016a5f, + 0x16a6000016a6a, + 0x16a7000016abf, + 0x16ac000016aca, + 0x16ad000016aee, + 0x16af000016af5, + 0x16b0000016b37, + 0x16b4000016b44, + 0x16b5000016b5a, + 0x16b6300016b78, + 0x16b7d00016b90, + 0x16e6000016e80, + 0x16f0000016f4b, + 0x16f4f00016f88, + 0x16f8f00016fa0, + 0x16fe000016fe2, + 0x16fe300016fe5, + 0x16ff000016ff2, + 0x17000000187f8, + 0x1880000018cd6, + 0x18d0000018d09, + 0x1aff00001aff4, + 0x1aff50001affc, + 0x1affd0001afff, + 0x1b0000001b123, + 0x1b1320001b133, + 0x1b1500001b153, + 0x1b1550001b156, + 0x1b1640001b168, + 0x1b1700001b2fc, + 0x1bc000001bc6b, + 0x1bc700001bc7d, + 0x1bc800001bc89, + 0x1bc900001bc9a, + 0x1bc9d0001bc9f, + 0x1cf000001cf2e, + 0x1cf300001cf47, + 0x1da000001da37, + 0x1da3b0001da6d, + 0x1da750001da76, + 0x1da840001da85, + 0x1da9b0001daa0, + 0x1daa10001dab0, + 0x1df000001df1f, + 0x1df250001df2b, + 0x1e0000001e007, + 0x1e0080001e019, + 0x1e01b0001e022, + 0x1e0230001e025, + 0x1e0260001e02b, + 0x1e0300001e06e, + 0x1e08f0001e090, + 0x1e1000001e12d, + 0x1e1300001e13e, + 0x1e1400001e14a, + 0x1e14e0001e14f, + 0x1e2900001e2af, + 0x1e2c00001e2fa, + 0x1e4d00001e4fa, + 0x1e7e00001e7e7, + 0x1e7e80001e7ec, + 0x1e7ed0001e7ef, + 0x1e7f00001e7ff, + 0x1e8000001e8c5, + 0x1e8d00001e8d7, + 0x1e9220001e94c, + 0x1e9500001e95a, + 0x200000002a6e0, + 0x2a7000002b73a, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x300000003134b, + 0x31350000323b0, + ), + 'CONTEXTJ': ( + 0x200c0000200e, + ), + 'CONTEXTO': ( + 0xb7000000b8, + 0x37500000376, + 0x5f3000005f5, + 0x6600000066a, + 0x6f0000006fa, + 0x30fb000030fc, + ), +} diff --git a/lib/idna/intranges.py b/lib/idna/intranges.py new file mode 100644 index 0000000..6a43b04 --- /dev/null +++ b/lib/idna/intranges.py @@ -0,0 +1,54 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect +from typing import List, Tuple + +def intranges_from_list(list_: List[int]) -> Tuple[int, ...]: + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i+1 < len(sorted_list): + if sorted_list[i] == sorted_list[i+1]-1: + continue + current_range = sorted_list[last_write+1:i+1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + +def _encode_range(start: int, end: int) -> int: + return (start << 32) | end + +def _decode_range(r: int) -> Tuple[int, int]: + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool: + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos-1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/lib/idna/package_data.py b/lib/idna/package_data.py new file mode 100644 index 0000000..8501893 --- /dev/null +++ b/lib/idna/package_data.py @@ -0,0 +1,2 @@ +__version__ = '3.4' + diff --git a/lib/idna/py.typed b/lib/idna/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lib/idna/uts46data.py b/lib/idna/uts46data.py new file mode 100644 index 0000000..186796c --- /dev/null +++ b/lib/idna/uts46data.py @@ -0,0 +1,8600 @@ +# This file is automatically generated by tools/idna-data +# vim: set fileencoding=utf-8 : + +from typing import List, Tuple, Union + + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = '15.0.0' +def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x0, '3'), + (0x1, '3'), + (0x2, '3'), + (0x3, '3'), + (0x4, '3'), + (0x5, '3'), + (0x6, '3'), + (0x7, '3'), + (0x8, '3'), + (0x9, '3'), + (0xA, '3'), + (0xB, '3'), + (0xC, '3'), + (0xD, '3'), + (0xE, '3'), + (0xF, '3'), + (0x10, '3'), + (0x11, '3'), + (0x12, '3'), + (0x13, '3'), + (0x14, '3'), + (0x15, '3'), + (0x16, '3'), + (0x17, '3'), + (0x18, '3'), + (0x19, '3'), + (0x1A, '3'), + (0x1B, '3'), + (0x1C, '3'), + (0x1D, '3'), + (0x1E, '3'), + (0x1F, '3'), + (0x20, '3'), + (0x21, '3'), + (0x22, '3'), + (0x23, '3'), + (0x24, '3'), + (0x25, '3'), + (0x26, '3'), + (0x27, '3'), + (0x28, '3'), + (0x29, '3'), + (0x2A, '3'), + (0x2B, '3'), + (0x2C, '3'), + (0x2D, 'V'), + (0x2E, 'V'), + (0x2F, '3'), + (0x30, 'V'), + (0x31, 'V'), + (0x32, 'V'), + (0x33, 'V'), + (0x34, 'V'), + (0x35, 'V'), + (0x36, 'V'), + (0x37, 'V'), + (0x38, 'V'), + (0x39, 'V'), + (0x3A, '3'), + (0x3B, '3'), + (0x3C, '3'), + (0x3D, '3'), + (0x3E, '3'), + (0x3F, '3'), + (0x40, '3'), + (0x41, 'M', 'a'), + (0x42, 'M', 'b'), + (0x43, 'M', 'c'), + (0x44, 'M', 'd'), + (0x45, 'M', 'e'), + (0x46, 'M', 'f'), + (0x47, 'M', 'g'), + (0x48, 'M', 'h'), + (0x49, 'M', 'i'), + (0x4A, 'M', 'j'), + (0x4B, 'M', 'k'), + (0x4C, 'M', 'l'), + (0x4D, 'M', 'm'), + (0x4E, 'M', 'n'), + (0x4F, 'M', 'o'), + (0x50, 'M', 'p'), + (0x51, 'M', 'q'), + (0x52, 'M', 'r'), + (0x53, 'M', 's'), + (0x54, 'M', 't'), + (0x55, 'M', 'u'), + (0x56, 'M', 'v'), + (0x57, 'M', 'w'), + (0x58, 'M', 'x'), + (0x59, 'M', 'y'), + (0x5A, 'M', 'z'), + (0x5B, '3'), + (0x5C, '3'), + (0x5D, '3'), + (0x5E, '3'), + (0x5F, '3'), + (0x60, '3'), + (0x61, 'V'), + (0x62, 'V'), + (0x63, 'V'), + ] + +def _seg_1() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x64, 'V'), + (0x65, 'V'), + (0x66, 'V'), + (0x67, 'V'), + (0x68, 'V'), + (0x69, 'V'), + (0x6A, 'V'), + (0x6B, 'V'), + (0x6C, 'V'), + (0x6D, 'V'), + (0x6E, 'V'), + (0x6F, 'V'), + (0x70, 'V'), + (0x71, 'V'), + (0x72, 'V'), + (0x73, 'V'), + (0x74, 'V'), + (0x75, 'V'), + (0x76, 'V'), + (0x77, 'V'), + (0x78, 'V'), + (0x79, 'V'), + (0x7A, 'V'), + (0x7B, '3'), + (0x7C, '3'), + (0x7D, '3'), + (0x7E, '3'), + (0x7F, '3'), + (0x80, 'X'), + (0x81, 'X'), + (0x82, 'X'), + (0x83, 'X'), + (0x84, 'X'), + (0x85, 'X'), + (0x86, 'X'), + (0x87, 'X'), + (0x88, 'X'), + (0x89, 'X'), + (0x8A, 'X'), + (0x8B, 'X'), + (0x8C, 'X'), + (0x8D, 'X'), + (0x8E, 'X'), + (0x8F, 'X'), + (0x90, 'X'), + (0x91, 'X'), + (0x92, 'X'), + (0x93, 'X'), + (0x94, 'X'), + (0x95, 'X'), + (0x96, 'X'), + (0x97, 'X'), + (0x98, 'X'), + (0x99, 'X'), + (0x9A, 'X'), + (0x9B, 'X'), + (0x9C, 'X'), + (0x9D, 'X'), + (0x9E, 'X'), + (0x9F, 'X'), + (0xA0, '3', ' '), + (0xA1, 'V'), + (0xA2, 'V'), + (0xA3, 'V'), + (0xA4, 'V'), + (0xA5, 'V'), + (0xA6, 'V'), + (0xA7, 'V'), + (0xA8, '3', ' ̈'), + (0xA9, 'V'), + (0xAA, 'M', 'a'), + (0xAB, 'V'), + (0xAC, 'V'), + (0xAD, 'I'), + (0xAE, 'V'), + (0xAF, '3', ' ̄'), + (0xB0, 'V'), + (0xB1, 'V'), + (0xB2, 'M', '2'), + (0xB3, 'M', '3'), + (0xB4, '3', ' ́'), + (0xB5, 'M', 'μ'), + (0xB6, 'V'), + (0xB7, 'V'), + (0xB8, '3', ' ̧'), + (0xB9, 'M', '1'), + (0xBA, 'M', 'o'), + (0xBB, 'V'), + (0xBC, 'M', '1⁄4'), + (0xBD, 'M', '1⁄2'), + (0xBE, 'M', '3⁄4'), + (0xBF, 'V'), + (0xC0, 'M', 'à'), + (0xC1, 'M', 'á'), + (0xC2, 'M', 'â'), + (0xC3, 'M', 'ã'), + (0xC4, 'M', 'ä'), + (0xC5, 'M', 'å'), + (0xC6, 'M', 'æ'), + (0xC7, 'M', 'ç'), + ] + +def _seg_2() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xC8, 'M', 'è'), + (0xC9, 'M', 'é'), + (0xCA, 'M', 'ê'), + (0xCB, 'M', 'ë'), + (0xCC, 'M', 'ì'), + (0xCD, 'M', 'í'), + (0xCE, 'M', 'î'), + (0xCF, 'M', 'ï'), + (0xD0, 'M', 'ð'), + (0xD1, 'M', 'ñ'), + (0xD2, 'M', 'ò'), + (0xD3, 'M', 'ó'), + (0xD4, 'M', 'ô'), + (0xD5, 'M', 'õ'), + (0xD6, 'M', 'ö'), + (0xD7, 'V'), + (0xD8, 'M', 'ø'), + (0xD9, 'M', 'ù'), + (0xDA, 'M', 'ú'), + (0xDB, 'M', 'û'), + (0xDC, 'M', 'ü'), + (0xDD, 'M', 'ý'), + (0xDE, 'M', 'þ'), + (0xDF, 'D', 'ss'), + (0xE0, 'V'), + (0xE1, 'V'), + (0xE2, 'V'), + (0xE3, 'V'), + (0xE4, 'V'), + (0xE5, 'V'), + (0xE6, 'V'), + (0xE7, 'V'), + (0xE8, 'V'), + (0xE9, 'V'), + (0xEA, 'V'), + (0xEB, 'V'), + (0xEC, 'V'), + (0xED, 'V'), + (0xEE, 'V'), + (0xEF, 'V'), + (0xF0, 'V'), + (0xF1, 'V'), + (0xF2, 'V'), + (0xF3, 'V'), + (0xF4, 'V'), + (0xF5, 'V'), + (0xF6, 'V'), + (0xF7, 'V'), + (0xF8, 'V'), + (0xF9, 'V'), + (0xFA, 'V'), + (0xFB, 'V'), + (0xFC, 'V'), + (0xFD, 'V'), + (0xFE, 'V'), + (0xFF, 'V'), + (0x100, 'M', 'ā'), + (0x101, 'V'), + (0x102, 'M', 'ă'), + (0x103, 'V'), + (0x104, 'M', 'ą'), + (0x105, 'V'), + (0x106, 'M', 'ć'), + (0x107, 'V'), + (0x108, 'M', 'ĉ'), + (0x109, 'V'), + (0x10A, 'M', 'ċ'), + (0x10B, 'V'), + (0x10C, 'M', 'č'), + (0x10D, 'V'), + (0x10E, 'M', 'ď'), + (0x10F, 'V'), + (0x110, 'M', 'đ'), + (0x111, 'V'), + (0x112, 'M', 'ē'), + (0x113, 'V'), + (0x114, 'M', 'ĕ'), + (0x115, 'V'), + (0x116, 'M', 'ė'), + (0x117, 'V'), + (0x118, 'M', 'ę'), + (0x119, 'V'), + (0x11A, 'M', 'ě'), + (0x11B, 'V'), + (0x11C, 'M', 'ĝ'), + (0x11D, 'V'), + (0x11E, 'M', 'ğ'), + (0x11F, 'V'), + (0x120, 'M', 'ġ'), + (0x121, 'V'), + (0x122, 'M', 'ģ'), + (0x123, 'V'), + (0x124, 'M', 'ĥ'), + (0x125, 'V'), + (0x126, 'M', 'ħ'), + (0x127, 'V'), + (0x128, 'M', 'ĩ'), + (0x129, 'V'), + (0x12A, 'M', 'ī'), + (0x12B, 'V'), + ] + +def _seg_3() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x12C, 'M', 'ĭ'), + (0x12D, 'V'), + (0x12E, 'M', 'į'), + (0x12F, 'V'), + (0x130, 'M', 'i̇'), + (0x131, 'V'), + (0x132, 'M', 'ij'), + (0x134, 'M', 'ĵ'), + (0x135, 'V'), + (0x136, 'M', 'ķ'), + (0x137, 'V'), + (0x139, 'M', 'ĺ'), + (0x13A, 'V'), + (0x13B, 'M', 'ļ'), + (0x13C, 'V'), + (0x13D, 'M', 'ľ'), + (0x13E, 'V'), + (0x13F, 'M', 'l·'), + (0x141, 'M', 'ł'), + (0x142, 'V'), + (0x143, 'M', 'ń'), + (0x144, 'V'), + (0x145, 'M', 'ņ'), + (0x146, 'V'), + (0x147, 'M', 'ň'), + (0x148, 'V'), + (0x149, 'M', 'ʼn'), + (0x14A, 'M', 'ŋ'), + (0x14B, 'V'), + (0x14C, 'M', 'ō'), + (0x14D, 'V'), + (0x14E, 'M', 'ŏ'), + (0x14F, 'V'), + (0x150, 'M', 'ő'), + (0x151, 'V'), + (0x152, 'M', 'œ'), + (0x153, 'V'), + (0x154, 'M', 'ŕ'), + (0x155, 'V'), + (0x156, 'M', 'ŗ'), + (0x157, 'V'), + (0x158, 'M', 'ř'), + (0x159, 'V'), + (0x15A, 'M', 'ś'), + (0x15B, 'V'), + (0x15C, 'M', 'ŝ'), + (0x15D, 'V'), + (0x15E, 'M', 'ş'), + (0x15F, 'V'), + (0x160, 'M', 'š'), + (0x161, 'V'), + (0x162, 'M', 'ţ'), + (0x163, 'V'), + (0x164, 'M', 'ť'), + (0x165, 'V'), + (0x166, 'M', 'ŧ'), + (0x167, 'V'), + (0x168, 'M', 'ũ'), + (0x169, 'V'), + (0x16A, 'M', 'ū'), + (0x16B, 'V'), + (0x16C, 'M', 'ŭ'), + (0x16D, 'V'), + (0x16E, 'M', 'ů'), + (0x16F, 'V'), + (0x170, 'M', 'ű'), + (0x171, 'V'), + (0x172, 'M', 'ų'), + (0x173, 'V'), + (0x174, 'M', 'ŵ'), + (0x175, 'V'), + (0x176, 'M', 'ŷ'), + (0x177, 'V'), + (0x178, 'M', 'ÿ'), + (0x179, 'M', 'ź'), + (0x17A, 'V'), + (0x17B, 'M', 'ż'), + (0x17C, 'V'), + (0x17D, 'M', 'ž'), + (0x17E, 'V'), + (0x17F, 'M', 's'), + (0x180, 'V'), + (0x181, 'M', 'ɓ'), + (0x182, 'M', 'ƃ'), + (0x183, 'V'), + (0x184, 'M', 'ƅ'), + (0x185, 'V'), + (0x186, 'M', 'ɔ'), + (0x187, 'M', 'ƈ'), + (0x188, 'V'), + (0x189, 'M', 'ɖ'), + (0x18A, 'M', 'ɗ'), + (0x18B, 'M', 'ƌ'), + (0x18C, 'V'), + (0x18E, 'M', 'ǝ'), + (0x18F, 'M', 'ə'), + (0x190, 'M', 'ɛ'), + (0x191, 'M', 'ƒ'), + (0x192, 'V'), + (0x193, 'M', 'ɠ'), + ] + +def _seg_4() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x194, 'M', 'ɣ'), + (0x195, 'V'), + (0x196, 'M', 'ɩ'), + (0x197, 'M', 'ɨ'), + (0x198, 'M', 'ƙ'), + (0x199, 'V'), + (0x19C, 'M', 'ɯ'), + (0x19D, 'M', 'ɲ'), + (0x19E, 'V'), + (0x19F, 'M', 'ɵ'), + (0x1A0, 'M', 'ơ'), + (0x1A1, 'V'), + (0x1A2, 'M', 'ƣ'), + (0x1A3, 'V'), + (0x1A4, 'M', 'ƥ'), + (0x1A5, 'V'), + (0x1A6, 'M', 'ʀ'), + (0x1A7, 'M', 'ƨ'), + (0x1A8, 'V'), + (0x1A9, 'M', 'ʃ'), + (0x1AA, 'V'), + (0x1AC, 'M', 'ƭ'), + (0x1AD, 'V'), + (0x1AE, 'M', 'ʈ'), + (0x1AF, 'M', 'ư'), + (0x1B0, 'V'), + (0x1B1, 'M', 'ʊ'), + (0x1B2, 'M', 'ʋ'), + (0x1B3, 'M', 'ƴ'), + (0x1B4, 'V'), + (0x1B5, 'M', 'ƶ'), + (0x1B6, 'V'), + (0x1B7, 'M', 'ʒ'), + (0x1B8, 'M', 'ƹ'), + (0x1B9, 'V'), + (0x1BC, 'M', 'ƽ'), + (0x1BD, 'V'), + (0x1C4, 'M', 'dž'), + (0x1C7, 'M', 'lj'), + (0x1CA, 'M', 'nj'), + (0x1CD, 'M', 'ǎ'), + (0x1CE, 'V'), + (0x1CF, 'M', 'ǐ'), + (0x1D0, 'V'), + (0x1D1, 'M', 'ǒ'), + (0x1D2, 'V'), + (0x1D3, 'M', 'ǔ'), + (0x1D4, 'V'), + (0x1D5, 'M', 'ǖ'), + (0x1D6, 'V'), + (0x1D7, 'M', 'ǘ'), + (0x1D8, 'V'), + (0x1D9, 'M', 'ǚ'), + (0x1DA, 'V'), + (0x1DB, 'M', 'ǜ'), + (0x1DC, 'V'), + (0x1DE, 'M', 'ǟ'), + (0x1DF, 'V'), + (0x1E0, 'M', 'ǡ'), + (0x1E1, 'V'), + (0x1E2, 'M', 'ǣ'), + (0x1E3, 'V'), + (0x1E4, 'M', 'ǥ'), + (0x1E5, 'V'), + (0x1E6, 'M', 'ǧ'), + (0x1E7, 'V'), + (0x1E8, 'M', 'ǩ'), + (0x1E9, 'V'), + (0x1EA, 'M', 'ǫ'), + (0x1EB, 'V'), + (0x1EC, 'M', 'ǭ'), + (0x1ED, 'V'), + (0x1EE, 'M', 'ǯ'), + (0x1EF, 'V'), + (0x1F1, 'M', 'dz'), + (0x1F4, 'M', 'ǵ'), + (0x1F5, 'V'), + (0x1F6, 'M', 'ƕ'), + (0x1F7, 'M', 'ƿ'), + (0x1F8, 'M', 'ǹ'), + (0x1F9, 'V'), + (0x1FA, 'M', 'ǻ'), + (0x1FB, 'V'), + (0x1FC, 'M', 'ǽ'), + (0x1FD, 'V'), + (0x1FE, 'M', 'ǿ'), + (0x1FF, 'V'), + (0x200, 'M', 'ȁ'), + (0x201, 'V'), + (0x202, 'M', 'ȃ'), + (0x203, 'V'), + (0x204, 'M', 'ȅ'), + (0x205, 'V'), + (0x206, 'M', 'ȇ'), + (0x207, 'V'), + (0x208, 'M', 'ȉ'), + (0x209, 'V'), + (0x20A, 'M', 'ȋ'), + (0x20B, 'V'), + (0x20C, 'M', 'ȍ'), + ] + +def _seg_5() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x20D, 'V'), + (0x20E, 'M', 'ȏ'), + (0x20F, 'V'), + (0x210, 'M', 'ȑ'), + (0x211, 'V'), + (0x212, 'M', 'ȓ'), + (0x213, 'V'), + (0x214, 'M', 'ȕ'), + (0x215, 'V'), + (0x216, 'M', 'ȗ'), + (0x217, 'V'), + (0x218, 'M', 'ș'), + (0x219, 'V'), + (0x21A, 'M', 'ț'), + (0x21B, 'V'), + (0x21C, 'M', 'ȝ'), + (0x21D, 'V'), + (0x21E, 'M', 'ȟ'), + (0x21F, 'V'), + (0x220, 'M', 'ƞ'), + (0x221, 'V'), + (0x222, 'M', 'ȣ'), + (0x223, 'V'), + (0x224, 'M', 'ȥ'), + (0x225, 'V'), + (0x226, 'M', 'ȧ'), + (0x227, 'V'), + (0x228, 'M', 'ȩ'), + (0x229, 'V'), + (0x22A, 'M', 'ȫ'), + (0x22B, 'V'), + (0x22C, 'M', 'ȭ'), + (0x22D, 'V'), + (0x22E, 'M', 'ȯ'), + (0x22F, 'V'), + (0x230, 'M', 'ȱ'), + (0x231, 'V'), + (0x232, 'M', 'ȳ'), + (0x233, 'V'), + (0x23A, 'M', 'ⱥ'), + (0x23B, 'M', 'ȼ'), + (0x23C, 'V'), + (0x23D, 'M', 'ƚ'), + (0x23E, 'M', 'ⱦ'), + (0x23F, 'V'), + (0x241, 'M', 'ɂ'), + (0x242, 'V'), + (0x243, 'M', 'ƀ'), + (0x244, 'M', 'ʉ'), + (0x245, 'M', 'ʌ'), + (0x246, 'M', 'ɇ'), + (0x247, 'V'), + (0x248, 'M', 'ɉ'), + (0x249, 'V'), + (0x24A, 'M', 'ɋ'), + (0x24B, 'V'), + (0x24C, 'M', 'ɍ'), + (0x24D, 'V'), + (0x24E, 'M', 'ɏ'), + (0x24F, 'V'), + (0x2B0, 'M', 'h'), + (0x2B1, 'M', 'ɦ'), + (0x2B2, 'M', 'j'), + (0x2B3, 'M', 'r'), + (0x2B4, 'M', 'ɹ'), + (0x2B5, 'M', 'ɻ'), + (0x2B6, 'M', 'ʁ'), + (0x2B7, 'M', 'w'), + (0x2B8, 'M', 'y'), + (0x2B9, 'V'), + (0x2D8, '3', ' ̆'), + (0x2D9, '3', ' ̇'), + (0x2DA, '3', ' ̊'), + (0x2DB, '3', ' ̨'), + (0x2DC, '3', ' ̃'), + (0x2DD, '3', ' ̋'), + (0x2DE, 'V'), + (0x2E0, 'M', 'ɣ'), + (0x2E1, 'M', 'l'), + (0x2E2, 'M', 's'), + (0x2E3, 'M', 'x'), + (0x2E4, 'M', 'ʕ'), + (0x2E5, 'V'), + (0x340, 'M', '̀'), + (0x341, 'M', '́'), + (0x342, 'V'), + (0x343, 'M', '̓'), + (0x344, 'M', '̈́'), + (0x345, 'M', 'ι'), + (0x346, 'V'), + (0x34F, 'I'), + (0x350, 'V'), + (0x370, 'M', 'ͱ'), + (0x371, 'V'), + (0x372, 'M', 'ͳ'), + (0x373, 'V'), + (0x374, 'M', 'ʹ'), + (0x375, 'V'), + (0x376, 'M', 'ͷ'), + (0x377, 'V'), + ] + +def _seg_6() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x378, 'X'), + (0x37A, '3', ' ι'), + (0x37B, 'V'), + (0x37E, '3', ';'), + (0x37F, 'M', 'ϳ'), + (0x380, 'X'), + (0x384, '3', ' ́'), + (0x385, '3', ' ̈́'), + (0x386, 'M', 'ά'), + (0x387, 'M', '·'), + (0x388, 'M', 'έ'), + (0x389, 'M', 'ή'), + (0x38A, 'M', 'ί'), + (0x38B, 'X'), + (0x38C, 'M', 'ό'), + (0x38D, 'X'), + (0x38E, 'M', 'ύ'), + (0x38F, 'M', 'ώ'), + (0x390, 'V'), + (0x391, 'M', 'α'), + (0x392, 'M', 'β'), + (0x393, 'M', 'γ'), + (0x394, 'M', 'δ'), + (0x395, 'M', 'ε'), + (0x396, 'M', 'ζ'), + (0x397, 'M', 'η'), + (0x398, 'M', 'θ'), + (0x399, 'M', 'ι'), + (0x39A, 'M', 'κ'), + (0x39B, 'M', 'λ'), + (0x39C, 'M', 'μ'), + (0x39D, 'M', 'ν'), + (0x39E, 'M', 'ξ'), + (0x39F, 'M', 'ο'), + (0x3A0, 'M', 'π'), + (0x3A1, 'M', 'ρ'), + (0x3A2, 'X'), + (0x3A3, 'M', 'σ'), + (0x3A4, 'M', 'τ'), + (0x3A5, 'M', 'υ'), + (0x3A6, 'M', 'φ'), + (0x3A7, 'M', 'χ'), + (0x3A8, 'M', 'ψ'), + (0x3A9, 'M', 'ω'), + (0x3AA, 'M', 'ϊ'), + (0x3AB, 'M', 'ϋ'), + (0x3AC, 'V'), + (0x3C2, 'D', 'σ'), + (0x3C3, 'V'), + (0x3CF, 'M', 'ϗ'), + (0x3D0, 'M', 'β'), + (0x3D1, 'M', 'θ'), + (0x3D2, 'M', 'υ'), + (0x3D3, 'M', 'ύ'), + (0x3D4, 'M', 'ϋ'), + (0x3D5, 'M', 'φ'), + (0x3D6, 'M', 'π'), + (0x3D7, 'V'), + (0x3D8, 'M', 'ϙ'), + (0x3D9, 'V'), + (0x3DA, 'M', 'ϛ'), + (0x3DB, 'V'), + (0x3DC, 'M', 'ϝ'), + (0x3DD, 'V'), + (0x3DE, 'M', 'ϟ'), + (0x3DF, 'V'), + (0x3E0, 'M', 'ϡ'), + (0x3E1, 'V'), + (0x3E2, 'M', 'ϣ'), + (0x3E3, 'V'), + (0x3E4, 'M', 'ϥ'), + (0x3E5, 'V'), + (0x3E6, 'M', 'ϧ'), + (0x3E7, 'V'), + (0x3E8, 'M', 'ϩ'), + (0x3E9, 'V'), + (0x3EA, 'M', 'ϫ'), + (0x3EB, 'V'), + (0x3EC, 'M', 'ϭ'), + (0x3ED, 'V'), + (0x3EE, 'M', 'ϯ'), + (0x3EF, 'V'), + (0x3F0, 'M', 'κ'), + (0x3F1, 'M', 'ρ'), + (0x3F2, 'M', 'σ'), + (0x3F3, 'V'), + (0x3F4, 'M', 'θ'), + (0x3F5, 'M', 'ε'), + (0x3F6, 'V'), + (0x3F7, 'M', 'ϸ'), + (0x3F8, 'V'), + (0x3F9, 'M', 'σ'), + (0x3FA, 'M', 'ϻ'), + (0x3FB, 'V'), + (0x3FD, 'M', 'ͻ'), + (0x3FE, 'M', 'ͼ'), + (0x3FF, 'M', 'ͽ'), + (0x400, 'M', 'ѐ'), + (0x401, 'M', 'ё'), + (0x402, 'M', 'ђ'), + ] + +def _seg_7() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x403, 'M', 'ѓ'), + (0x404, 'M', 'є'), + (0x405, 'M', 'ѕ'), + (0x406, 'M', 'і'), + (0x407, 'M', 'ї'), + (0x408, 'M', 'ј'), + (0x409, 'M', 'љ'), + (0x40A, 'M', 'њ'), + (0x40B, 'M', 'ћ'), + (0x40C, 'M', 'ќ'), + (0x40D, 'M', 'ѝ'), + (0x40E, 'M', 'ў'), + (0x40F, 'M', 'џ'), + (0x410, 'M', 'а'), + (0x411, 'M', 'б'), + (0x412, 'M', 'в'), + (0x413, 'M', 'г'), + (0x414, 'M', 'д'), + (0x415, 'M', 'е'), + (0x416, 'M', 'ж'), + (0x417, 'M', 'з'), + (0x418, 'M', 'и'), + (0x419, 'M', 'й'), + (0x41A, 'M', 'к'), + (0x41B, 'M', 'л'), + (0x41C, 'M', 'м'), + (0x41D, 'M', 'н'), + (0x41E, 'M', 'о'), + (0x41F, 'M', 'п'), + (0x420, 'M', 'р'), + (0x421, 'M', 'с'), + (0x422, 'M', 'т'), + (0x423, 'M', 'у'), + (0x424, 'M', 'ф'), + (0x425, 'M', 'х'), + (0x426, 'M', 'ц'), + (0x427, 'M', 'ч'), + (0x428, 'M', 'ш'), + (0x429, 'M', 'щ'), + (0x42A, 'M', 'ъ'), + (0x42B, 'M', 'ы'), + (0x42C, 'M', 'ь'), + (0x42D, 'M', 'э'), + (0x42E, 'M', 'ю'), + (0x42F, 'M', 'я'), + (0x430, 'V'), + (0x460, 'M', 'ѡ'), + (0x461, 'V'), + (0x462, 'M', 'ѣ'), + (0x463, 'V'), + (0x464, 'M', 'ѥ'), + (0x465, 'V'), + (0x466, 'M', 'ѧ'), + (0x467, 'V'), + (0x468, 'M', 'ѩ'), + (0x469, 'V'), + (0x46A, 'M', 'ѫ'), + (0x46B, 'V'), + (0x46C, 'M', 'ѭ'), + (0x46D, 'V'), + (0x46E, 'M', 'ѯ'), + (0x46F, 'V'), + (0x470, 'M', 'ѱ'), + (0x471, 'V'), + (0x472, 'M', 'ѳ'), + (0x473, 'V'), + (0x474, 'M', 'ѵ'), + (0x475, 'V'), + (0x476, 'M', 'ѷ'), + (0x477, 'V'), + (0x478, 'M', 'ѹ'), + (0x479, 'V'), + (0x47A, 'M', 'ѻ'), + (0x47B, 'V'), + (0x47C, 'M', 'ѽ'), + (0x47D, 'V'), + (0x47E, 'M', 'ѿ'), + (0x47F, 'V'), + (0x480, 'M', 'ҁ'), + (0x481, 'V'), + (0x48A, 'M', 'ҋ'), + (0x48B, 'V'), + (0x48C, 'M', 'ҍ'), + (0x48D, 'V'), + (0x48E, 'M', 'ҏ'), + (0x48F, 'V'), + (0x490, 'M', 'ґ'), + (0x491, 'V'), + (0x492, 'M', 'ғ'), + (0x493, 'V'), + (0x494, 'M', 'ҕ'), + (0x495, 'V'), + (0x496, 'M', 'җ'), + (0x497, 'V'), + (0x498, 'M', 'ҙ'), + (0x499, 'V'), + (0x49A, 'M', 'қ'), + (0x49B, 'V'), + (0x49C, 'M', 'ҝ'), + (0x49D, 'V'), + ] + +def _seg_8() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x49E, 'M', 'ҟ'), + (0x49F, 'V'), + (0x4A0, 'M', 'ҡ'), + (0x4A1, 'V'), + (0x4A2, 'M', 'ң'), + (0x4A3, 'V'), + (0x4A4, 'M', 'ҥ'), + (0x4A5, 'V'), + (0x4A6, 'M', 'ҧ'), + (0x4A7, 'V'), + (0x4A8, 'M', 'ҩ'), + (0x4A9, 'V'), + (0x4AA, 'M', 'ҫ'), + (0x4AB, 'V'), + (0x4AC, 'M', 'ҭ'), + (0x4AD, 'V'), + (0x4AE, 'M', 'ү'), + (0x4AF, 'V'), + (0x4B0, 'M', 'ұ'), + (0x4B1, 'V'), + (0x4B2, 'M', 'ҳ'), + (0x4B3, 'V'), + (0x4B4, 'M', 'ҵ'), + (0x4B5, 'V'), + (0x4B6, 'M', 'ҷ'), + (0x4B7, 'V'), + (0x4B8, 'M', 'ҹ'), + (0x4B9, 'V'), + (0x4BA, 'M', 'һ'), + (0x4BB, 'V'), + (0x4BC, 'M', 'ҽ'), + (0x4BD, 'V'), + (0x4BE, 'M', 'ҿ'), + (0x4BF, 'V'), + (0x4C0, 'X'), + (0x4C1, 'M', 'ӂ'), + (0x4C2, 'V'), + (0x4C3, 'M', 'ӄ'), + (0x4C4, 'V'), + (0x4C5, 'M', 'ӆ'), + (0x4C6, 'V'), + (0x4C7, 'M', 'ӈ'), + (0x4C8, 'V'), + (0x4C9, 'M', 'ӊ'), + (0x4CA, 'V'), + (0x4CB, 'M', 'ӌ'), + (0x4CC, 'V'), + (0x4CD, 'M', 'ӎ'), + (0x4CE, 'V'), + (0x4D0, 'M', 'ӑ'), + (0x4D1, 'V'), + (0x4D2, 'M', 'ӓ'), + (0x4D3, 'V'), + (0x4D4, 'M', 'ӕ'), + (0x4D5, 'V'), + (0x4D6, 'M', 'ӗ'), + (0x4D7, 'V'), + (0x4D8, 'M', 'ә'), + (0x4D9, 'V'), + (0x4DA, 'M', 'ӛ'), + (0x4DB, 'V'), + (0x4DC, 'M', 'ӝ'), + (0x4DD, 'V'), + (0x4DE, 'M', 'ӟ'), + (0x4DF, 'V'), + (0x4E0, 'M', 'ӡ'), + (0x4E1, 'V'), + (0x4E2, 'M', 'ӣ'), + (0x4E3, 'V'), + (0x4E4, 'M', 'ӥ'), + (0x4E5, 'V'), + (0x4E6, 'M', 'ӧ'), + (0x4E7, 'V'), + (0x4E8, 'M', 'ө'), + (0x4E9, 'V'), + (0x4EA, 'M', 'ӫ'), + (0x4EB, 'V'), + (0x4EC, 'M', 'ӭ'), + (0x4ED, 'V'), + (0x4EE, 'M', 'ӯ'), + (0x4EF, 'V'), + (0x4F0, 'M', 'ӱ'), + (0x4F1, 'V'), + (0x4F2, 'M', 'ӳ'), + (0x4F3, 'V'), + (0x4F4, 'M', 'ӵ'), + (0x4F5, 'V'), + (0x4F6, 'M', 'ӷ'), + (0x4F7, 'V'), + (0x4F8, 'M', 'ӹ'), + (0x4F9, 'V'), + (0x4FA, 'M', 'ӻ'), + (0x4FB, 'V'), + (0x4FC, 'M', 'ӽ'), + (0x4FD, 'V'), + (0x4FE, 'M', 'ӿ'), + (0x4FF, 'V'), + (0x500, 'M', 'ԁ'), + (0x501, 'V'), + (0x502, 'M', 'ԃ'), + ] + +def _seg_9() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x503, 'V'), + (0x504, 'M', 'ԅ'), + (0x505, 'V'), + (0x506, 'M', 'ԇ'), + (0x507, 'V'), + (0x508, 'M', 'ԉ'), + (0x509, 'V'), + (0x50A, 'M', 'ԋ'), + (0x50B, 'V'), + (0x50C, 'M', 'ԍ'), + (0x50D, 'V'), + (0x50E, 'M', 'ԏ'), + (0x50F, 'V'), + (0x510, 'M', 'ԑ'), + (0x511, 'V'), + (0x512, 'M', 'ԓ'), + (0x513, 'V'), + (0x514, 'M', 'ԕ'), + (0x515, 'V'), + (0x516, 'M', 'ԗ'), + (0x517, 'V'), + (0x518, 'M', 'ԙ'), + (0x519, 'V'), + (0x51A, 'M', 'ԛ'), + (0x51B, 'V'), + (0x51C, 'M', 'ԝ'), + (0x51D, 'V'), + (0x51E, 'M', 'ԟ'), + (0x51F, 'V'), + (0x520, 'M', 'ԡ'), + (0x521, 'V'), + (0x522, 'M', 'ԣ'), + (0x523, 'V'), + (0x524, 'M', 'ԥ'), + (0x525, 'V'), + (0x526, 'M', 'ԧ'), + (0x527, 'V'), + (0x528, 'M', 'ԩ'), + (0x529, 'V'), + (0x52A, 'M', 'ԫ'), + (0x52B, 'V'), + (0x52C, 'M', 'ԭ'), + (0x52D, 'V'), + (0x52E, 'M', 'ԯ'), + (0x52F, 'V'), + (0x530, 'X'), + (0x531, 'M', 'ա'), + (0x532, 'M', 'բ'), + (0x533, 'M', 'գ'), + (0x534, 'M', 'դ'), + (0x535, 'M', 'ե'), + (0x536, 'M', 'զ'), + (0x537, 'M', 'է'), + (0x538, 'M', 'ը'), + (0x539, 'M', 'թ'), + (0x53A, 'M', 'ժ'), + (0x53B, 'M', 'ի'), + (0x53C, 'M', 'լ'), + (0x53D, 'M', 'խ'), + (0x53E, 'M', 'ծ'), + (0x53F, 'M', 'կ'), + (0x540, 'M', 'հ'), + (0x541, 'M', 'ձ'), + (0x542, 'M', 'ղ'), + (0x543, 'M', 'ճ'), + (0x544, 'M', 'մ'), + (0x545, 'M', 'յ'), + (0x546, 'M', 'ն'), + (0x547, 'M', 'շ'), + (0x548, 'M', 'ո'), + (0x549, 'M', 'չ'), + (0x54A, 'M', 'պ'), + (0x54B, 'M', 'ջ'), + (0x54C, 'M', 'ռ'), + (0x54D, 'M', 'ս'), + (0x54E, 'M', 'վ'), + (0x54F, 'M', 'տ'), + (0x550, 'M', 'ր'), + (0x551, 'M', 'ց'), + (0x552, 'M', 'ւ'), + (0x553, 'M', 'փ'), + (0x554, 'M', 'ք'), + (0x555, 'M', 'օ'), + (0x556, 'M', 'ֆ'), + (0x557, 'X'), + (0x559, 'V'), + (0x587, 'M', 'եւ'), + (0x588, 'V'), + (0x58B, 'X'), + (0x58D, 'V'), + (0x590, 'X'), + (0x591, 'V'), + (0x5C8, 'X'), + (0x5D0, 'V'), + (0x5EB, 'X'), + (0x5EF, 'V'), + (0x5F5, 'X'), + (0x606, 'V'), + (0x61C, 'X'), + (0x61D, 'V'), + ] + +def _seg_10() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x675, 'M', 'اٴ'), + (0x676, 'M', 'وٴ'), + (0x677, 'M', 'ۇٴ'), + (0x678, 'M', 'يٴ'), + (0x679, 'V'), + (0x6DD, 'X'), + (0x6DE, 'V'), + (0x70E, 'X'), + (0x710, 'V'), + (0x74B, 'X'), + (0x74D, 'V'), + (0x7B2, 'X'), + (0x7C0, 'V'), + (0x7FB, 'X'), + (0x7FD, 'V'), + (0x82E, 'X'), + (0x830, 'V'), + (0x83F, 'X'), + (0x840, 'V'), + (0x85C, 'X'), + (0x85E, 'V'), + (0x85F, 'X'), + (0x860, 'V'), + (0x86B, 'X'), + (0x870, 'V'), + (0x88F, 'X'), + (0x898, 'V'), + (0x8E2, 'X'), + (0x8E3, 'V'), + (0x958, 'M', 'क़'), + (0x959, 'M', 'ख़'), + (0x95A, 'M', 'ग़'), + (0x95B, 'M', 'ज़'), + (0x95C, 'M', 'ड़'), + (0x95D, 'M', 'ढ़'), + (0x95E, 'M', 'फ़'), + (0x95F, 'M', 'य़'), + (0x960, 'V'), + (0x984, 'X'), + (0x985, 'V'), + (0x98D, 'X'), + (0x98F, 'V'), + (0x991, 'X'), + (0x993, 'V'), + (0x9A9, 'X'), + (0x9AA, 'V'), + (0x9B1, 'X'), + (0x9B2, 'V'), + (0x9B3, 'X'), + (0x9B6, 'V'), + (0x9BA, 'X'), + (0x9BC, 'V'), + (0x9C5, 'X'), + (0x9C7, 'V'), + (0x9C9, 'X'), + (0x9CB, 'V'), + (0x9CF, 'X'), + (0x9D7, 'V'), + (0x9D8, 'X'), + (0x9DC, 'M', 'ড়'), + (0x9DD, 'M', 'ঢ়'), + (0x9DE, 'X'), + (0x9DF, 'M', 'য়'), + (0x9E0, 'V'), + (0x9E4, 'X'), + (0x9E6, 'V'), + (0x9FF, 'X'), + (0xA01, 'V'), + (0xA04, 'X'), + (0xA05, 'V'), + (0xA0B, 'X'), + (0xA0F, 'V'), + (0xA11, 'X'), + (0xA13, 'V'), + (0xA29, 'X'), + (0xA2A, 'V'), + (0xA31, 'X'), + (0xA32, 'V'), + (0xA33, 'M', 'ਲ਼'), + (0xA34, 'X'), + (0xA35, 'V'), + (0xA36, 'M', 'ਸ਼'), + (0xA37, 'X'), + (0xA38, 'V'), + (0xA3A, 'X'), + (0xA3C, 'V'), + (0xA3D, 'X'), + (0xA3E, 'V'), + (0xA43, 'X'), + (0xA47, 'V'), + (0xA49, 'X'), + (0xA4B, 'V'), + (0xA4E, 'X'), + (0xA51, 'V'), + (0xA52, 'X'), + (0xA59, 'M', 'ਖ਼'), + (0xA5A, 'M', 'ਗ਼'), + (0xA5B, 'M', 'ਜ਼'), + (0xA5C, 'V'), + (0xA5D, 'X'), + ] + +def _seg_11() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA5E, 'M', 'ਫ਼'), + (0xA5F, 'X'), + (0xA66, 'V'), + (0xA77, 'X'), + (0xA81, 'V'), + (0xA84, 'X'), + (0xA85, 'V'), + (0xA8E, 'X'), + (0xA8F, 'V'), + (0xA92, 'X'), + (0xA93, 'V'), + (0xAA9, 'X'), + (0xAAA, 'V'), + (0xAB1, 'X'), + (0xAB2, 'V'), + (0xAB4, 'X'), + (0xAB5, 'V'), + (0xABA, 'X'), + (0xABC, 'V'), + (0xAC6, 'X'), + (0xAC7, 'V'), + (0xACA, 'X'), + (0xACB, 'V'), + (0xACE, 'X'), + (0xAD0, 'V'), + (0xAD1, 'X'), + (0xAE0, 'V'), + (0xAE4, 'X'), + (0xAE6, 'V'), + (0xAF2, 'X'), + (0xAF9, 'V'), + (0xB00, 'X'), + (0xB01, 'V'), + (0xB04, 'X'), + (0xB05, 'V'), + (0xB0D, 'X'), + (0xB0F, 'V'), + (0xB11, 'X'), + (0xB13, 'V'), + (0xB29, 'X'), + (0xB2A, 'V'), + (0xB31, 'X'), + (0xB32, 'V'), + (0xB34, 'X'), + (0xB35, 'V'), + (0xB3A, 'X'), + (0xB3C, 'V'), + (0xB45, 'X'), + (0xB47, 'V'), + (0xB49, 'X'), + (0xB4B, 'V'), + (0xB4E, 'X'), + (0xB55, 'V'), + (0xB58, 'X'), + (0xB5C, 'M', 'ଡ଼'), + (0xB5D, 'M', 'ଢ଼'), + (0xB5E, 'X'), + (0xB5F, 'V'), + (0xB64, 'X'), + (0xB66, 'V'), + (0xB78, 'X'), + (0xB82, 'V'), + (0xB84, 'X'), + (0xB85, 'V'), + (0xB8B, 'X'), + (0xB8E, 'V'), + (0xB91, 'X'), + (0xB92, 'V'), + (0xB96, 'X'), + (0xB99, 'V'), + (0xB9B, 'X'), + (0xB9C, 'V'), + (0xB9D, 'X'), + (0xB9E, 'V'), + (0xBA0, 'X'), + (0xBA3, 'V'), + (0xBA5, 'X'), + (0xBA8, 'V'), + (0xBAB, 'X'), + (0xBAE, 'V'), + (0xBBA, 'X'), + (0xBBE, 'V'), + (0xBC3, 'X'), + (0xBC6, 'V'), + (0xBC9, 'X'), + (0xBCA, 'V'), + (0xBCE, 'X'), + (0xBD0, 'V'), + (0xBD1, 'X'), + (0xBD7, 'V'), + (0xBD8, 'X'), + (0xBE6, 'V'), + (0xBFB, 'X'), + (0xC00, 'V'), + (0xC0D, 'X'), + (0xC0E, 'V'), + (0xC11, 'X'), + (0xC12, 'V'), + (0xC29, 'X'), + (0xC2A, 'V'), + ] + +def _seg_12() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xC3A, 'X'), + (0xC3C, 'V'), + (0xC45, 'X'), + (0xC46, 'V'), + (0xC49, 'X'), + (0xC4A, 'V'), + (0xC4E, 'X'), + (0xC55, 'V'), + (0xC57, 'X'), + (0xC58, 'V'), + (0xC5B, 'X'), + (0xC5D, 'V'), + (0xC5E, 'X'), + (0xC60, 'V'), + (0xC64, 'X'), + (0xC66, 'V'), + (0xC70, 'X'), + (0xC77, 'V'), + (0xC8D, 'X'), + (0xC8E, 'V'), + (0xC91, 'X'), + (0xC92, 'V'), + (0xCA9, 'X'), + (0xCAA, 'V'), + (0xCB4, 'X'), + (0xCB5, 'V'), + (0xCBA, 'X'), + (0xCBC, 'V'), + (0xCC5, 'X'), + (0xCC6, 'V'), + (0xCC9, 'X'), + (0xCCA, 'V'), + (0xCCE, 'X'), + (0xCD5, 'V'), + (0xCD7, 'X'), + (0xCDD, 'V'), + (0xCDF, 'X'), + (0xCE0, 'V'), + (0xCE4, 'X'), + (0xCE6, 'V'), + (0xCF0, 'X'), + (0xCF1, 'V'), + (0xCF4, 'X'), + (0xD00, 'V'), + (0xD0D, 'X'), + (0xD0E, 'V'), + (0xD11, 'X'), + (0xD12, 'V'), + (0xD45, 'X'), + (0xD46, 'V'), + (0xD49, 'X'), + (0xD4A, 'V'), + (0xD50, 'X'), + (0xD54, 'V'), + (0xD64, 'X'), + (0xD66, 'V'), + (0xD80, 'X'), + (0xD81, 'V'), + (0xD84, 'X'), + (0xD85, 'V'), + (0xD97, 'X'), + (0xD9A, 'V'), + (0xDB2, 'X'), + (0xDB3, 'V'), + (0xDBC, 'X'), + (0xDBD, 'V'), + (0xDBE, 'X'), + (0xDC0, 'V'), + (0xDC7, 'X'), + (0xDCA, 'V'), + (0xDCB, 'X'), + (0xDCF, 'V'), + (0xDD5, 'X'), + (0xDD6, 'V'), + (0xDD7, 'X'), + (0xDD8, 'V'), + (0xDE0, 'X'), + (0xDE6, 'V'), + (0xDF0, 'X'), + (0xDF2, 'V'), + (0xDF5, 'X'), + (0xE01, 'V'), + (0xE33, 'M', 'ํา'), + (0xE34, 'V'), + (0xE3B, 'X'), + (0xE3F, 'V'), + (0xE5C, 'X'), + (0xE81, 'V'), + (0xE83, 'X'), + (0xE84, 'V'), + (0xE85, 'X'), + (0xE86, 'V'), + (0xE8B, 'X'), + (0xE8C, 'V'), + (0xEA4, 'X'), + (0xEA5, 'V'), + (0xEA6, 'X'), + (0xEA7, 'V'), + (0xEB3, 'M', 'ໍາ'), + (0xEB4, 'V'), + ] + +def _seg_13() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xEBE, 'X'), + (0xEC0, 'V'), + (0xEC5, 'X'), + (0xEC6, 'V'), + (0xEC7, 'X'), + (0xEC8, 'V'), + (0xECF, 'X'), + (0xED0, 'V'), + (0xEDA, 'X'), + (0xEDC, 'M', 'ຫນ'), + (0xEDD, 'M', 'ຫມ'), + (0xEDE, 'V'), + (0xEE0, 'X'), + (0xF00, 'V'), + (0xF0C, 'M', '་'), + (0xF0D, 'V'), + (0xF43, 'M', 'གྷ'), + (0xF44, 'V'), + (0xF48, 'X'), + (0xF49, 'V'), + (0xF4D, 'M', 'ཌྷ'), + (0xF4E, 'V'), + (0xF52, 'M', 'དྷ'), + (0xF53, 'V'), + (0xF57, 'M', 'བྷ'), + (0xF58, 'V'), + (0xF5C, 'M', 'ཛྷ'), + (0xF5D, 'V'), + (0xF69, 'M', 'ཀྵ'), + (0xF6A, 'V'), + (0xF6D, 'X'), + (0xF71, 'V'), + (0xF73, 'M', 'ཱི'), + (0xF74, 'V'), + (0xF75, 'M', 'ཱུ'), + (0xF76, 'M', 'ྲྀ'), + (0xF77, 'M', 'ྲཱྀ'), + (0xF78, 'M', 'ླྀ'), + (0xF79, 'M', 'ླཱྀ'), + (0xF7A, 'V'), + (0xF81, 'M', 'ཱྀ'), + (0xF82, 'V'), + (0xF93, 'M', 'ྒྷ'), + (0xF94, 'V'), + (0xF98, 'X'), + (0xF99, 'V'), + (0xF9D, 'M', 'ྜྷ'), + (0xF9E, 'V'), + (0xFA2, 'M', 'ྡྷ'), + (0xFA3, 'V'), + (0xFA7, 'M', 'ྦྷ'), + (0xFA8, 'V'), + (0xFAC, 'M', 'ྫྷ'), + (0xFAD, 'V'), + (0xFB9, 'M', 'ྐྵ'), + (0xFBA, 'V'), + (0xFBD, 'X'), + (0xFBE, 'V'), + (0xFCD, 'X'), + (0xFCE, 'V'), + (0xFDB, 'X'), + (0x1000, 'V'), + (0x10A0, 'X'), + (0x10C7, 'M', 'ⴧ'), + (0x10C8, 'X'), + (0x10CD, 'M', 'ⴭ'), + (0x10CE, 'X'), + (0x10D0, 'V'), + (0x10FC, 'M', 'ნ'), + (0x10FD, 'V'), + (0x115F, 'X'), + (0x1161, 'V'), + (0x1249, 'X'), + (0x124A, 'V'), + (0x124E, 'X'), + (0x1250, 'V'), + (0x1257, 'X'), + (0x1258, 'V'), + (0x1259, 'X'), + (0x125A, 'V'), + (0x125E, 'X'), + (0x1260, 'V'), + (0x1289, 'X'), + (0x128A, 'V'), + (0x128E, 'X'), + (0x1290, 'V'), + (0x12B1, 'X'), + (0x12B2, 'V'), + (0x12B6, 'X'), + (0x12B8, 'V'), + (0x12BF, 'X'), + (0x12C0, 'V'), + (0x12C1, 'X'), + (0x12C2, 'V'), + (0x12C6, 'X'), + (0x12C8, 'V'), + (0x12D7, 'X'), + (0x12D8, 'V'), + (0x1311, 'X'), + (0x1312, 'V'), + ] + +def _seg_14() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1316, 'X'), + (0x1318, 'V'), + (0x135B, 'X'), + (0x135D, 'V'), + (0x137D, 'X'), + (0x1380, 'V'), + (0x139A, 'X'), + (0x13A0, 'V'), + (0x13F6, 'X'), + (0x13F8, 'M', 'Ᏸ'), + (0x13F9, 'M', 'Ᏹ'), + (0x13FA, 'M', 'Ᏺ'), + (0x13FB, 'M', 'Ᏻ'), + (0x13FC, 'M', 'Ᏼ'), + (0x13FD, 'M', 'Ᏽ'), + (0x13FE, 'X'), + (0x1400, 'V'), + (0x1680, 'X'), + (0x1681, 'V'), + (0x169D, 'X'), + (0x16A0, 'V'), + (0x16F9, 'X'), + (0x1700, 'V'), + (0x1716, 'X'), + (0x171F, 'V'), + (0x1737, 'X'), + (0x1740, 'V'), + (0x1754, 'X'), + (0x1760, 'V'), + (0x176D, 'X'), + (0x176E, 'V'), + (0x1771, 'X'), + (0x1772, 'V'), + (0x1774, 'X'), + (0x1780, 'V'), + (0x17B4, 'X'), + (0x17B6, 'V'), + (0x17DE, 'X'), + (0x17E0, 'V'), + (0x17EA, 'X'), + (0x17F0, 'V'), + (0x17FA, 'X'), + (0x1800, 'V'), + (0x1806, 'X'), + (0x1807, 'V'), + (0x180B, 'I'), + (0x180E, 'X'), + (0x180F, 'I'), + (0x1810, 'V'), + (0x181A, 'X'), + (0x1820, 'V'), + (0x1879, 'X'), + (0x1880, 'V'), + (0x18AB, 'X'), + (0x18B0, 'V'), + (0x18F6, 'X'), + (0x1900, 'V'), + (0x191F, 'X'), + (0x1920, 'V'), + (0x192C, 'X'), + (0x1930, 'V'), + (0x193C, 'X'), + (0x1940, 'V'), + (0x1941, 'X'), + (0x1944, 'V'), + (0x196E, 'X'), + (0x1970, 'V'), + (0x1975, 'X'), + (0x1980, 'V'), + (0x19AC, 'X'), + (0x19B0, 'V'), + (0x19CA, 'X'), + (0x19D0, 'V'), + (0x19DB, 'X'), + (0x19DE, 'V'), + (0x1A1C, 'X'), + (0x1A1E, 'V'), + (0x1A5F, 'X'), + (0x1A60, 'V'), + (0x1A7D, 'X'), + (0x1A7F, 'V'), + (0x1A8A, 'X'), + (0x1A90, 'V'), + (0x1A9A, 'X'), + (0x1AA0, 'V'), + (0x1AAE, 'X'), + (0x1AB0, 'V'), + (0x1ACF, 'X'), + (0x1B00, 'V'), + (0x1B4D, 'X'), + (0x1B50, 'V'), + (0x1B7F, 'X'), + (0x1B80, 'V'), + (0x1BF4, 'X'), + (0x1BFC, 'V'), + (0x1C38, 'X'), + (0x1C3B, 'V'), + (0x1C4A, 'X'), + (0x1C4D, 'V'), + (0x1C80, 'M', 'в'), + ] + +def _seg_15() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1C81, 'M', 'д'), + (0x1C82, 'M', 'о'), + (0x1C83, 'M', 'с'), + (0x1C84, 'M', 'т'), + (0x1C86, 'M', 'ъ'), + (0x1C87, 'M', 'ѣ'), + (0x1C88, 'M', 'ꙋ'), + (0x1C89, 'X'), + (0x1C90, 'M', 'ა'), + (0x1C91, 'M', 'ბ'), + (0x1C92, 'M', 'გ'), + (0x1C93, 'M', 'დ'), + (0x1C94, 'M', 'ე'), + (0x1C95, 'M', 'ვ'), + (0x1C96, 'M', 'ზ'), + (0x1C97, 'M', 'თ'), + (0x1C98, 'M', 'ი'), + (0x1C99, 'M', 'კ'), + (0x1C9A, 'M', 'ლ'), + (0x1C9B, 'M', 'მ'), + (0x1C9C, 'M', 'ნ'), + (0x1C9D, 'M', 'ო'), + (0x1C9E, 'M', 'პ'), + (0x1C9F, 'M', 'ჟ'), + (0x1CA0, 'M', 'რ'), + (0x1CA1, 'M', 'ს'), + (0x1CA2, 'M', 'ტ'), + (0x1CA3, 'M', 'უ'), + (0x1CA4, 'M', 'ფ'), + (0x1CA5, 'M', 'ქ'), + (0x1CA6, 'M', 'ღ'), + (0x1CA7, 'M', 'ყ'), + (0x1CA8, 'M', 'შ'), + (0x1CA9, 'M', 'ჩ'), + (0x1CAA, 'M', 'ც'), + (0x1CAB, 'M', 'ძ'), + (0x1CAC, 'M', 'წ'), + (0x1CAD, 'M', 'ჭ'), + (0x1CAE, 'M', 'ხ'), + (0x1CAF, 'M', 'ჯ'), + (0x1CB0, 'M', 'ჰ'), + (0x1CB1, 'M', 'ჱ'), + (0x1CB2, 'M', 'ჲ'), + (0x1CB3, 'M', 'ჳ'), + (0x1CB4, 'M', 'ჴ'), + (0x1CB5, 'M', 'ჵ'), + (0x1CB6, 'M', 'ჶ'), + (0x1CB7, 'M', 'ჷ'), + (0x1CB8, 'M', 'ჸ'), + (0x1CB9, 'M', 'ჹ'), + (0x1CBA, 'M', 'ჺ'), + (0x1CBB, 'X'), + (0x1CBD, 'M', 'ჽ'), + (0x1CBE, 'M', 'ჾ'), + (0x1CBF, 'M', 'ჿ'), + (0x1CC0, 'V'), + (0x1CC8, 'X'), + (0x1CD0, 'V'), + (0x1CFB, 'X'), + (0x1D00, 'V'), + (0x1D2C, 'M', 'a'), + (0x1D2D, 'M', 'æ'), + (0x1D2E, 'M', 'b'), + (0x1D2F, 'V'), + (0x1D30, 'M', 'd'), + (0x1D31, 'M', 'e'), + (0x1D32, 'M', 'ǝ'), + (0x1D33, 'M', 'g'), + (0x1D34, 'M', 'h'), + (0x1D35, 'M', 'i'), + (0x1D36, 'M', 'j'), + (0x1D37, 'M', 'k'), + (0x1D38, 'M', 'l'), + (0x1D39, 'M', 'm'), + (0x1D3A, 'M', 'n'), + (0x1D3B, 'V'), + (0x1D3C, 'M', 'o'), + (0x1D3D, 'M', 'ȣ'), + (0x1D3E, 'M', 'p'), + (0x1D3F, 'M', 'r'), + (0x1D40, 'M', 't'), + (0x1D41, 'M', 'u'), + (0x1D42, 'M', 'w'), + (0x1D43, 'M', 'a'), + (0x1D44, 'M', 'ɐ'), + (0x1D45, 'M', 'ɑ'), + (0x1D46, 'M', 'ᴂ'), + (0x1D47, 'M', 'b'), + (0x1D48, 'M', 'd'), + (0x1D49, 'M', 'e'), + (0x1D4A, 'M', 'ə'), + (0x1D4B, 'M', 'ɛ'), + (0x1D4C, 'M', 'ɜ'), + (0x1D4D, 'M', 'g'), + (0x1D4E, 'V'), + (0x1D4F, 'M', 'k'), + (0x1D50, 'M', 'm'), + (0x1D51, 'M', 'ŋ'), + (0x1D52, 'M', 'o'), + (0x1D53, 'M', 'ɔ'), + ] + +def _seg_16() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D54, 'M', 'ᴖ'), + (0x1D55, 'M', 'ᴗ'), + (0x1D56, 'M', 'p'), + (0x1D57, 'M', 't'), + (0x1D58, 'M', 'u'), + (0x1D59, 'M', 'ᴝ'), + (0x1D5A, 'M', 'ɯ'), + (0x1D5B, 'M', 'v'), + (0x1D5C, 'M', 'ᴥ'), + (0x1D5D, 'M', 'β'), + (0x1D5E, 'M', 'γ'), + (0x1D5F, 'M', 'δ'), + (0x1D60, 'M', 'φ'), + (0x1D61, 'M', 'χ'), + (0x1D62, 'M', 'i'), + (0x1D63, 'M', 'r'), + (0x1D64, 'M', 'u'), + (0x1D65, 'M', 'v'), + (0x1D66, 'M', 'β'), + (0x1D67, 'M', 'γ'), + (0x1D68, 'M', 'ρ'), + (0x1D69, 'M', 'φ'), + (0x1D6A, 'M', 'χ'), + (0x1D6B, 'V'), + (0x1D78, 'M', 'н'), + (0x1D79, 'V'), + (0x1D9B, 'M', 'ɒ'), + (0x1D9C, 'M', 'c'), + (0x1D9D, 'M', 'ɕ'), + (0x1D9E, 'M', 'ð'), + (0x1D9F, 'M', 'ɜ'), + (0x1DA0, 'M', 'f'), + (0x1DA1, 'M', 'ɟ'), + (0x1DA2, 'M', 'ɡ'), + (0x1DA3, 'M', 'ɥ'), + (0x1DA4, 'M', 'ɨ'), + (0x1DA5, 'M', 'ɩ'), + (0x1DA6, 'M', 'ɪ'), + (0x1DA7, 'M', 'ᵻ'), + (0x1DA8, 'M', 'ʝ'), + (0x1DA9, 'M', 'ɭ'), + (0x1DAA, 'M', 'ᶅ'), + (0x1DAB, 'M', 'ʟ'), + (0x1DAC, 'M', 'ɱ'), + (0x1DAD, 'M', 'ɰ'), + (0x1DAE, 'M', 'ɲ'), + (0x1DAF, 'M', 'ɳ'), + (0x1DB0, 'M', 'ɴ'), + (0x1DB1, 'M', 'ɵ'), + (0x1DB2, 'M', 'ɸ'), + (0x1DB3, 'M', 'ʂ'), + (0x1DB4, 'M', 'ʃ'), + (0x1DB5, 'M', 'ƫ'), + (0x1DB6, 'M', 'ʉ'), + (0x1DB7, 'M', 'ʊ'), + (0x1DB8, 'M', 'ᴜ'), + (0x1DB9, 'M', 'ʋ'), + (0x1DBA, 'M', 'ʌ'), + (0x1DBB, 'M', 'z'), + (0x1DBC, 'M', 'ʐ'), + (0x1DBD, 'M', 'ʑ'), + (0x1DBE, 'M', 'ʒ'), + (0x1DBF, 'M', 'θ'), + (0x1DC0, 'V'), + (0x1E00, 'M', 'ḁ'), + (0x1E01, 'V'), + (0x1E02, 'M', 'ḃ'), + (0x1E03, 'V'), + (0x1E04, 'M', 'ḅ'), + (0x1E05, 'V'), + (0x1E06, 'M', 'ḇ'), + (0x1E07, 'V'), + (0x1E08, 'M', 'ḉ'), + (0x1E09, 'V'), + (0x1E0A, 'M', 'ḋ'), + (0x1E0B, 'V'), + (0x1E0C, 'M', 'ḍ'), + (0x1E0D, 'V'), + (0x1E0E, 'M', 'ḏ'), + (0x1E0F, 'V'), + (0x1E10, 'M', 'ḑ'), + (0x1E11, 'V'), + (0x1E12, 'M', 'ḓ'), + (0x1E13, 'V'), + (0x1E14, 'M', 'ḕ'), + (0x1E15, 'V'), + (0x1E16, 'M', 'ḗ'), + (0x1E17, 'V'), + (0x1E18, 'M', 'ḙ'), + (0x1E19, 'V'), + (0x1E1A, 'M', 'ḛ'), + (0x1E1B, 'V'), + (0x1E1C, 'M', 'ḝ'), + (0x1E1D, 'V'), + (0x1E1E, 'M', 'ḟ'), + (0x1E1F, 'V'), + (0x1E20, 'M', 'ḡ'), + (0x1E21, 'V'), + (0x1E22, 'M', 'ḣ'), + (0x1E23, 'V'), + ] + +def _seg_17() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E24, 'M', 'ḥ'), + (0x1E25, 'V'), + (0x1E26, 'M', 'ḧ'), + (0x1E27, 'V'), + (0x1E28, 'M', 'ḩ'), + (0x1E29, 'V'), + (0x1E2A, 'M', 'ḫ'), + (0x1E2B, 'V'), + (0x1E2C, 'M', 'ḭ'), + (0x1E2D, 'V'), + (0x1E2E, 'M', 'ḯ'), + (0x1E2F, 'V'), + (0x1E30, 'M', 'ḱ'), + (0x1E31, 'V'), + (0x1E32, 'M', 'ḳ'), + (0x1E33, 'V'), + (0x1E34, 'M', 'ḵ'), + (0x1E35, 'V'), + (0x1E36, 'M', 'ḷ'), + (0x1E37, 'V'), + (0x1E38, 'M', 'ḹ'), + (0x1E39, 'V'), + (0x1E3A, 'M', 'ḻ'), + (0x1E3B, 'V'), + (0x1E3C, 'M', 'ḽ'), + (0x1E3D, 'V'), + (0x1E3E, 'M', 'ḿ'), + (0x1E3F, 'V'), + (0x1E40, 'M', 'ṁ'), + (0x1E41, 'V'), + (0x1E42, 'M', 'ṃ'), + (0x1E43, 'V'), + (0x1E44, 'M', 'ṅ'), + (0x1E45, 'V'), + (0x1E46, 'M', 'ṇ'), + (0x1E47, 'V'), + (0x1E48, 'M', 'ṉ'), + (0x1E49, 'V'), + (0x1E4A, 'M', 'ṋ'), + (0x1E4B, 'V'), + (0x1E4C, 'M', 'ṍ'), + (0x1E4D, 'V'), + (0x1E4E, 'M', 'ṏ'), + (0x1E4F, 'V'), + (0x1E50, 'M', 'ṑ'), + (0x1E51, 'V'), + (0x1E52, 'M', 'ṓ'), + (0x1E53, 'V'), + (0x1E54, 'M', 'ṕ'), + (0x1E55, 'V'), + (0x1E56, 'M', 'ṗ'), + (0x1E57, 'V'), + (0x1E58, 'M', 'ṙ'), + (0x1E59, 'V'), + (0x1E5A, 'M', 'ṛ'), + (0x1E5B, 'V'), + (0x1E5C, 'M', 'ṝ'), + (0x1E5D, 'V'), + (0x1E5E, 'M', 'ṟ'), + (0x1E5F, 'V'), + (0x1E60, 'M', 'ṡ'), + (0x1E61, 'V'), + (0x1E62, 'M', 'ṣ'), + (0x1E63, 'V'), + (0x1E64, 'M', 'ṥ'), + (0x1E65, 'V'), + (0x1E66, 'M', 'ṧ'), + (0x1E67, 'V'), + (0x1E68, 'M', 'ṩ'), + (0x1E69, 'V'), + (0x1E6A, 'M', 'ṫ'), + (0x1E6B, 'V'), + (0x1E6C, 'M', 'ṭ'), + (0x1E6D, 'V'), + (0x1E6E, 'M', 'ṯ'), + (0x1E6F, 'V'), + (0x1E70, 'M', 'ṱ'), + (0x1E71, 'V'), + (0x1E72, 'M', 'ṳ'), + (0x1E73, 'V'), + (0x1E74, 'M', 'ṵ'), + (0x1E75, 'V'), + (0x1E76, 'M', 'ṷ'), + (0x1E77, 'V'), + (0x1E78, 'M', 'ṹ'), + (0x1E79, 'V'), + (0x1E7A, 'M', 'ṻ'), + (0x1E7B, 'V'), + (0x1E7C, 'M', 'ṽ'), + (0x1E7D, 'V'), + (0x1E7E, 'M', 'ṿ'), + (0x1E7F, 'V'), + (0x1E80, 'M', 'ẁ'), + (0x1E81, 'V'), + (0x1E82, 'M', 'ẃ'), + (0x1E83, 'V'), + (0x1E84, 'M', 'ẅ'), + (0x1E85, 'V'), + (0x1E86, 'M', 'ẇ'), + (0x1E87, 'V'), + ] + +def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E88, 'M', 'ẉ'), + (0x1E89, 'V'), + (0x1E8A, 'M', 'ẋ'), + (0x1E8B, 'V'), + (0x1E8C, 'M', 'ẍ'), + (0x1E8D, 'V'), + (0x1E8E, 'M', 'ẏ'), + (0x1E8F, 'V'), + (0x1E90, 'M', 'ẑ'), + (0x1E91, 'V'), + (0x1E92, 'M', 'ẓ'), + (0x1E93, 'V'), + (0x1E94, 'M', 'ẕ'), + (0x1E95, 'V'), + (0x1E9A, 'M', 'aʾ'), + (0x1E9B, 'M', 'ṡ'), + (0x1E9C, 'V'), + (0x1E9E, 'M', 'ss'), + (0x1E9F, 'V'), + (0x1EA0, 'M', 'ạ'), + (0x1EA1, 'V'), + (0x1EA2, 'M', 'ả'), + (0x1EA3, 'V'), + (0x1EA4, 'M', 'ấ'), + (0x1EA5, 'V'), + (0x1EA6, 'M', 'ầ'), + (0x1EA7, 'V'), + (0x1EA8, 'M', 'ẩ'), + (0x1EA9, 'V'), + (0x1EAA, 'M', 'ẫ'), + (0x1EAB, 'V'), + (0x1EAC, 'M', 'ậ'), + (0x1EAD, 'V'), + (0x1EAE, 'M', 'ắ'), + (0x1EAF, 'V'), + (0x1EB0, 'M', 'ằ'), + (0x1EB1, 'V'), + (0x1EB2, 'M', 'ẳ'), + (0x1EB3, 'V'), + (0x1EB4, 'M', 'ẵ'), + (0x1EB5, 'V'), + (0x1EB6, 'M', 'ặ'), + (0x1EB7, 'V'), + (0x1EB8, 'M', 'ẹ'), + (0x1EB9, 'V'), + (0x1EBA, 'M', 'ẻ'), + (0x1EBB, 'V'), + (0x1EBC, 'M', 'ẽ'), + (0x1EBD, 'V'), + (0x1EBE, 'M', 'ế'), + (0x1EBF, 'V'), + (0x1EC0, 'M', 'ề'), + (0x1EC1, 'V'), + (0x1EC2, 'M', 'ể'), + (0x1EC3, 'V'), + (0x1EC4, 'M', 'ễ'), + (0x1EC5, 'V'), + (0x1EC6, 'M', 'ệ'), + (0x1EC7, 'V'), + (0x1EC8, 'M', 'ỉ'), + (0x1EC9, 'V'), + (0x1ECA, 'M', 'ị'), + (0x1ECB, 'V'), + (0x1ECC, 'M', 'ọ'), + (0x1ECD, 'V'), + (0x1ECE, 'M', 'ỏ'), + (0x1ECF, 'V'), + (0x1ED0, 'M', 'ố'), + (0x1ED1, 'V'), + (0x1ED2, 'M', 'ồ'), + (0x1ED3, 'V'), + (0x1ED4, 'M', 'ổ'), + (0x1ED5, 'V'), + (0x1ED6, 'M', 'ỗ'), + (0x1ED7, 'V'), + (0x1ED8, 'M', 'ộ'), + (0x1ED9, 'V'), + (0x1EDA, 'M', 'ớ'), + (0x1EDB, 'V'), + (0x1EDC, 'M', 'ờ'), + (0x1EDD, 'V'), + (0x1EDE, 'M', 'ở'), + (0x1EDF, 'V'), + (0x1EE0, 'M', 'ỡ'), + (0x1EE1, 'V'), + (0x1EE2, 'M', 'ợ'), + (0x1EE3, 'V'), + (0x1EE4, 'M', 'ụ'), + (0x1EE5, 'V'), + (0x1EE6, 'M', 'ủ'), + (0x1EE7, 'V'), + (0x1EE8, 'M', 'ứ'), + (0x1EE9, 'V'), + (0x1EEA, 'M', 'ừ'), + (0x1EEB, 'V'), + (0x1EEC, 'M', 'ử'), + (0x1EED, 'V'), + (0x1EEE, 'M', 'ữ'), + (0x1EEF, 'V'), + (0x1EF0, 'M', 'ự'), + ] + +def _seg_19() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EF1, 'V'), + (0x1EF2, 'M', 'ỳ'), + (0x1EF3, 'V'), + (0x1EF4, 'M', 'ỵ'), + (0x1EF5, 'V'), + (0x1EF6, 'M', 'ỷ'), + (0x1EF7, 'V'), + (0x1EF8, 'M', 'ỹ'), + (0x1EF9, 'V'), + (0x1EFA, 'M', 'ỻ'), + (0x1EFB, 'V'), + (0x1EFC, 'M', 'ỽ'), + (0x1EFD, 'V'), + (0x1EFE, 'M', 'ỿ'), + (0x1EFF, 'V'), + (0x1F08, 'M', 'ἀ'), + (0x1F09, 'M', 'ἁ'), + (0x1F0A, 'M', 'ἂ'), + (0x1F0B, 'M', 'ἃ'), + (0x1F0C, 'M', 'ἄ'), + (0x1F0D, 'M', 'ἅ'), + (0x1F0E, 'M', 'ἆ'), + (0x1F0F, 'M', 'ἇ'), + (0x1F10, 'V'), + (0x1F16, 'X'), + (0x1F18, 'M', 'ἐ'), + (0x1F19, 'M', 'ἑ'), + (0x1F1A, 'M', 'ἒ'), + (0x1F1B, 'M', 'ἓ'), + (0x1F1C, 'M', 'ἔ'), + (0x1F1D, 'M', 'ἕ'), + (0x1F1E, 'X'), + (0x1F20, 'V'), + (0x1F28, 'M', 'ἠ'), + (0x1F29, 'M', 'ἡ'), + (0x1F2A, 'M', 'ἢ'), + (0x1F2B, 'M', 'ἣ'), + (0x1F2C, 'M', 'ἤ'), + (0x1F2D, 'M', 'ἥ'), + (0x1F2E, 'M', 'ἦ'), + (0x1F2F, 'M', 'ἧ'), + (0x1F30, 'V'), + (0x1F38, 'M', 'ἰ'), + (0x1F39, 'M', 'ἱ'), + (0x1F3A, 'M', 'ἲ'), + (0x1F3B, 'M', 'ἳ'), + (0x1F3C, 'M', 'ἴ'), + (0x1F3D, 'M', 'ἵ'), + (0x1F3E, 'M', 'ἶ'), + (0x1F3F, 'M', 'ἷ'), + (0x1F40, 'V'), + (0x1F46, 'X'), + (0x1F48, 'M', 'ὀ'), + (0x1F49, 'M', 'ὁ'), + (0x1F4A, 'M', 'ὂ'), + (0x1F4B, 'M', 'ὃ'), + (0x1F4C, 'M', 'ὄ'), + (0x1F4D, 'M', 'ὅ'), + (0x1F4E, 'X'), + (0x1F50, 'V'), + (0x1F58, 'X'), + (0x1F59, 'M', 'ὑ'), + (0x1F5A, 'X'), + (0x1F5B, 'M', 'ὓ'), + (0x1F5C, 'X'), + (0x1F5D, 'M', 'ὕ'), + (0x1F5E, 'X'), + (0x1F5F, 'M', 'ὗ'), + (0x1F60, 'V'), + (0x1F68, 'M', 'ὠ'), + (0x1F69, 'M', 'ὡ'), + (0x1F6A, 'M', 'ὢ'), + (0x1F6B, 'M', 'ὣ'), + (0x1F6C, 'M', 'ὤ'), + (0x1F6D, 'M', 'ὥ'), + (0x1F6E, 'M', 'ὦ'), + (0x1F6F, 'M', 'ὧ'), + (0x1F70, 'V'), + (0x1F71, 'M', 'ά'), + (0x1F72, 'V'), + (0x1F73, 'M', 'έ'), + (0x1F74, 'V'), + (0x1F75, 'M', 'ή'), + (0x1F76, 'V'), + (0x1F77, 'M', 'ί'), + (0x1F78, 'V'), + (0x1F79, 'M', 'ό'), + (0x1F7A, 'V'), + (0x1F7B, 'M', 'ύ'), + (0x1F7C, 'V'), + (0x1F7D, 'M', 'ώ'), + (0x1F7E, 'X'), + (0x1F80, 'M', 'ἀι'), + (0x1F81, 'M', 'ἁι'), + (0x1F82, 'M', 'ἂι'), + (0x1F83, 'M', 'ἃι'), + (0x1F84, 'M', 'ἄι'), + (0x1F85, 'M', 'ἅι'), + (0x1F86, 'M', 'ἆι'), + (0x1F87, 'M', 'ἇι'), + ] + +def _seg_20() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1F88, 'M', 'ἀι'), + (0x1F89, 'M', 'ἁι'), + (0x1F8A, 'M', 'ἂι'), + (0x1F8B, 'M', 'ἃι'), + (0x1F8C, 'M', 'ἄι'), + (0x1F8D, 'M', 'ἅι'), + (0x1F8E, 'M', 'ἆι'), + (0x1F8F, 'M', 'ἇι'), + (0x1F90, 'M', 'ἠι'), + (0x1F91, 'M', 'ἡι'), + (0x1F92, 'M', 'ἢι'), + (0x1F93, 'M', 'ἣι'), + (0x1F94, 'M', 'ἤι'), + (0x1F95, 'M', 'ἥι'), + (0x1F96, 'M', 'ἦι'), + (0x1F97, 'M', 'ἧι'), + (0x1F98, 'M', 'ἠι'), + (0x1F99, 'M', 'ἡι'), + (0x1F9A, 'M', 'ἢι'), + (0x1F9B, 'M', 'ἣι'), + (0x1F9C, 'M', 'ἤι'), + (0x1F9D, 'M', 'ἥι'), + (0x1F9E, 'M', 'ἦι'), + (0x1F9F, 'M', 'ἧι'), + (0x1FA0, 'M', 'ὠι'), + (0x1FA1, 'M', 'ὡι'), + (0x1FA2, 'M', 'ὢι'), + (0x1FA3, 'M', 'ὣι'), + (0x1FA4, 'M', 'ὤι'), + (0x1FA5, 'M', 'ὥι'), + (0x1FA6, 'M', 'ὦι'), + (0x1FA7, 'M', 'ὧι'), + (0x1FA8, 'M', 'ὠι'), + (0x1FA9, 'M', 'ὡι'), + (0x1FAA, 'M', 'ὢι'), + (0x1FAB, 'M', 'ὣι'), + (0x1FAC, 'M', 'ὤι'), + (0x1FAD, 'M', 'ὥι'), + (0x1FAE, 'M', 'ὦι'), + (0x1FAF, 'M', 'ὧι'), + (0x1FB0, 'V'), + (0x1FB2, 'M', 'ὰι'), + (0x1FB3, 'M', 'αι'), + (0x1FB4, 'M', 'άι'), + (0x1FB5, 'X'), + (0x1FB6, 'V'), + (0x1FB7, 'M', 'ᾶι'), + (0x1FB8, 'M', 'ᾰ'), + (0x1FB9, 'M', 'ᾱ'), + (0x1FBA, 'M', 'ὰ'), + (0x1FBB, 'M', 'ά'), + (0x1FBC, 'M', 'αι'), + (0x1FBD, '3', ' ̓'), + (0x1FBE, 'M', 'ι'), + (0x1FBF, '3', ' ̓'), + (0x1FC0, '3', ' ͂'), + (0x1FC1, '3', ' ̈͂'), + (0x1FC2, 'M', 'ὴι'), + (0x1FC3, 'M', 'ηι'), + (0x1FC4, 'M', 'ήι'), + (0x1FC5, 'X'), + (0x1FC6, 'V'), + (0x1FC7, 'M', 'ῆι'), + (0x1FC8, 'M', 'ὲ'), + (0x1FC9, 'M', 'έ'), + (0x1FCA, 'M', 'ὴ'), + (0x1FCB, 'M', 'ή'), + (0x1FCC, 'M', 'ηι'), + (0x1FCD, '3', ' ̓̀'), + (0x1FCE, '3', ' ̓́'), + (0x1FCF, '3', ' ̓͂'), + (0x1FD0, 'V'), + (0x1FD3, 'M', 'ΐ'), + (0x1FD4, 'X'), + (0x1FD6, 'V'), + (0x1FD8, 'M', 'ῐ'), + (0x1FD9, 'M', 'ῑ'), + (0x1FDA, 'M', 'ὶ'), + (0x1FDB, 'M', 'ί'), + (0x1FDC, 'X'), + (0x1FDD, '3', ' ̔̀'), + (0x1FDE, '3', ' ̔́'), + (0x1FDF, '3', ' ̔͂'), + (0x1FE0, 'V'), + (0x1FE3, 'M', 'ΰ'), + (0x1FE4, 'V'), + (0x1FE8, 'M', 'ῠ'), + (0x1FE9, 'M', 'ῡ'), + (0x1FEA, 'M', 'ὺ'), + (0x1FEB, 'M', 'ύ'), + (0x1FEC, 'M', 'ῥ'), + (0x1FED, '3', ' ̈̀'), + (0x1FEE, '3', ' ̈́'), + (0x1FEF, '3', '`'), + (0x1FF0, 'X'), + (0x1FF2, 'M', 'ὼι'), + (0x1FF3, 'M', 'ωι'), + (0x1FF4, 'M', 'ώι'), + (0x1FF5, 'X'), + (0x1FF6, 'V'), + ] + +def _seg_21() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1FF7, 'M', 'ῶι'), + (0x1FF8, 'M', 'ὸ'), + (0x1FF9, 'M', 'ό'), + (0x1FFA, 'M', 'ὼ'), + (0x1FFB, 'M', 'ώ'), + (0x1FFC, 'M', 'ωι'), + (0x1FFD, '3', ' ́'), + (0x1FFE, '3', ' ̔'), + (0x1FFF, 'X'), + (0x2000, '3', ' '), + (0x200B, 'I'), + (0x200C, 'D', ''), + (0x200E, 'X'), + (0x2010, 'V'), + (0x2011, 'M', '‐'), + (0x2012, 'V'), + (0x2017, '3', ' ̳'), + (0x2018, 'V'), + (0x2024, 'X'), + (0x2027, 'V'), + (0x2028, 'X'), + (0x202F, '3', ' '), + (0x2030, 'V'), + (0x2033, 'M', '′′'), + (0x2034, 'M', '′′′'), + (0x2035, 'V'), + (0x2036, 'M', '‵‵'), + (0x2037, 'M', '‵‵‵'), + (0x2038, 'V'), + (0x203C, '3', '!!'), + (0x203D, 'V'), + (0x203E, '3', ' ̅'), + (0x203F, 'V'), + (0x2047, '3', '??'), + (0x2048, '3', '?!'), + (0x2049, '3', '!?'), + (0x204A, 'V'), + (0x2057, 'M', '′′′′'), + (0x2058, 'V'), + (0x205F, '3', ' '), + (0x2060, 'I'), + (0x2061, 'X'), + (0x2064, 'I'), + (0x2065, 'X'), + (0x2070, 'M', '0'), + (0x2071, 'M', 'i'), + (0x2072, 'X'), + (0x2074, 'M', '4'), + (0x2075, 'M', '5'), + (0x2076, 'M', '6'), + (0x2077, 'M', '7'), + (0x2078, 'M', '8'), + (0x2079, 'M', '9'), + (0x207A, '3', '+'), + (0x207B, 'M', '−'), + (0x207C, '3', '='), + (0x207D, '3', '('), + (0x207E, '3', ')'), + (0x207F, 'M', 'n'), + (0x2080, 'M', '0'), + (0x2081, 'M', '1'), + (0x2082, 'M', '2'), + (0x2083, 'M', '3'), + (0x2084, 'M', '4'), + (0x2085, 'M', '5'), + (0x2086, 'M', '6'), + (0x2087, 'M', '7'), + (0x2088, 'M', '8'), + (0x2089, 'M', '9'), + (0x208A, '3', '+'), + (0x208B, 'M', '−'), + (0x208C, '3', '='), + (0x208D, '3', '('), + (0x208E, '3', ')'), + (0x208F, 'X'), + (0x2090, 'M', 'a'), + (0x2091, 'M', 'e'), + (0x2092, 'M', 'o'), + (0x2093, 'M', 'x'), + (0x2094, 'M', 'ə'), + (0x2095, 'M', 'h'), + (0x2096, 'M', 'k'), + (0x2097, 'M', 'l'), + (0x2098, 'M', 'm'), + (0x2099, 'M', 'n'), + (0x209A, 'M', 'p'), + (0x209B, 'M', 's'), + (0x209C, 'M', 't'), + (0x209D, 'X'), + (0x20A0, 'V'), + (0x20A8, 'M', 'rs'), + (0x20A9, 'V'), + (0x20C1, 'X'), + (0x20D0, 'V'), + (0x20F1, 'X'), + (0x2100, '3', 'a/c'), + (0x2101, '3', 'a/s'), + (0x2102, 'M', 'c'), + (0x2103, 'M', '°c'), + (0x2104, 'V'), + ] + +def _seg_22() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2105, '3', 'c/o'), + (0x2106, '3', 'c/u'), + (0x2107, 'M', 'ɛ'), + (0x2108, 'V'), + (0x2109, 'M', '°f'), + (0x210A, 'M', 'g'), + (0x210B, 'M', 'h'), + (0x210F, 'M', 'ħ'), + (0x2110, 'M', 'i'), + (0x2112, 'M', 'l'), + (0x2114, 'V'), + (0x2115, 'M', 'n'), + (0x2116, 'M', 'no'), + (0x2117, 'V'), + (0x2119, 'M', 'p'), + (0x211A, 'M', 'q'), + (0x211B, 'M', 'r'), + (0x211E, 'V'), + (0x2120, 'M', 'sm'), + (0x2121, 'M', 'tel'), + (0x2122, 'M', 'tm'), + (0x2123, 'V'), + (0x2124, 'M', 'z'), + (0x2125, 'V'), + (0x2126, 'M', 'ω'), + (0x2127, 'V'), + (0x2128, 'M', 'z'), + (0x2129, 'V'), + (0x212A, 'M', 'k'), + (0x212B, 'M', 'å'), + (0x212C, 'M', 'b'), + (0x212D, 'M', 'c'), + (0x212E, 'V'), + (0x212F, 'M', 'e'), + (0x2131, 'M', 'f'), + (0x2132, 'X'), + (0x2133, 'M', 'm'), + (0x2134, 'M', 'o'), + (0x2135, 'M', 'א'), + (0x2136, 'M', 'ב'), + (0x2137, 'M', 'ג'), + (0x2138, 'M', 'ד'), + (0x2139, 'M', 'i'), + (0x213A, 'V'), + (0x213B, 'M', 'fax'), + (0x213C, 'M', 'π'), + (0x213D, 'M', 'γ'), + (0x213F, 'M', 'π'), + (0x2140, 'M', '∑'), + (0x2141, 'V'), + (0x2145, 'M', 'd'), + (0x2147, 'M', 'e'), + (0x2148, 'M', 'i'), + (0x2149, 'M', 'j'), + (0x214A, 'V'), + (0x2150, 'M', '1⁄7'), + (0x2151, 'M', '1⁄9'), + (0x2152, 'M', '1⁄10'), + (0x2153, 'M', '1⁄3'), + (0x2154, 'M', '2⁄3'), + (0x2155, 'M', '1⁄5'), + (0x2156, 'M', '2⁄5'), + (0x2157, 'M', '3⁄5'), + (0x2158, 'M', '4⁄5'), + (0x2159, 'M', '1⁄6'), + (0x215A, 'M', '5⁄6'), + (0x215B, 'M', '1⁄8'), + (0x215C, 'M', '3⁄8'), + (0x215D, 'M', '5⁄8'), + (0x215E, 'M', '7⁄8'), + (0x215F, 'M', '1⁄'), + (0x2160, 'M', 'i'), + (0x2161, 'M', 'ii'), + (0x2162, 'M', 'iii'), + (0x2163, 'M', 'iv'), + (0x2164, 'M', 'v'), + (0x2165, 'M', 'vi'), + (0x2166, 'M', 'vii'), + (0x2167, 'M', 'viii'), + (0x2168, 'M', 'ix'), + (0x2169, 'M', 'x'), + (0x216A, 'M', 'xi'), + (0x216B, 'M', 'xii'), + (0x216C, 'M', 'l'), + (0x216D, 'M', 'c'), + (0x216E, 'M', 'd'), + (0x216F, 'M', 'm'), + (0x2170, 'M', 'i'), + (0x2171, 'M', 'ii'), + (0x2172, 'M', 'iii'), + (0x2173, 'M', 'iv'), + (0x2174, 'M', 'v'), + (0x2175, 'M', 'vi'), + (0x2176, 'M', 'vii'), + (0x2177, 'M', 'viii'), + (0x2178, 'M', 'ix'), + (0x2179, 'M', 'x'), + (0x217A, 'M', 'xi'), + (0x217B, 'M', 'xii'), + (0x217C, 'M', 'l'), + ] + +def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x217D, 'M', 'c'), + (0x217E, 'M', 'd'), + (0x217F, 'M', 'm'), + (0x2180, 'V'), + (0x2183, 'X'), + (0x2184, 'V'), + (0x2189, 'M', '0⁄3'), + (0x218A, 'V'), + (0x218C, 'X'), + (0x2190, 'V'), + (0x222C, 'M', '∫∫'), + (0x222D, 'M', '∫∫∫'), + (0x222E, 'V'), + (0x222F, 'M', '∮∮'), + (0x2230, 'M', '∮∮∮'), + (0x2231, 'V'), + (0x2260, '3'), + (0x2261, 'V'), + (0x226E, '3'), + (0x2270, 'V'), + (0x2329, 'M', '〈'), + (0x232A, 'M', '〉'), + (0x232B, 'V'), + (0x2427, 'X'), + (0x2440, 'V'), + (0x244B, 'X'), + (0x2460, 'M', '1'), + (0x2461, 'M', '2'), + (0x2462, 'M', '3'), + (0x2463, 'M', '4'), + (0x2464, 'M', '5'), + (0x2465, 'M', '6'), + (0x2466, 'M', '7'), + (0x2467, 'M', '8'), + (0x2468, 'M', '9'), + (0x2469, 'M', '10'), + (0x246A, 'M', '11'), + (0x246B, 'M', '12'), + (0x246C, 'M', '13'), + (0x246D, 'M', '14'), + (0x246E, 'M', '15'), + (0x246F, 'M', '16'), + (0x2470, 'M', '17'), + (0x2471, 'M', '18'), + (0x2472, 'M', '19'), + (0x2473, 'M', '20'), + (0x2474, '3', '(1)'), + (0x2475, '3', '(2)'), + (0x2476, '3', '(3)'), + (0x2477, '3', '(4)'), + (0x2478, '3', '(5)'), + (0x2479, '3', '(6)'), + (0x247A, '3', '(7)'), + (0x247B, '3', '(8)'), + (0x247C, '3', '(9)'), + (0x247D, '3', '(10)'), + (0x247E, '3', '(11)'), + (0x247F, '3', '(12)'), + (0x2480, '3', '(13)'), + (0x2481, '3', '(14)'), + (0x2482, '3', '(15)'), + (0x2483, '3', '(16)'), + (0x2484, '3', '(17)'), + (0x2485, '3', '(18)'), + (0x2486, '3', '(19)'), + (0x2487, '3', '(20)'), + (0x2488, 'X'), + (0x249C, '3', '(a)'), + (0x249D, '3', '(b)'), + (0x249E, '3', '(c)'), + (0x249F, '3', '(d)'), + (0x24A0, '3', '(e)'), + (0x24A1, '3', '(f)'), + (0x24A2, '3', '(g)'), + (0x24A3, '3', '(h)'), + (0x24A4, '3', '(i)'), + (0x24A5, '3', '(j)'), + (0x24A6, '3', '(k)'), + (0x24A7, '3', '(l)'), + (0x24A8, '3', '(m)'), + (0x24A9, '3', '(n)'), + (0x24AA, '3', '(o)'), + (0x24AB, '3', '(p)'), + (0x24AC, '3', '(q)'), + (0x24AD, '3', '(r)'), + (0x24AE, '3', '(s)'), + (0x24AF, '3', '(t)'), + (0x24B0, '3', '(u)'), + (0x24B1, '3', '(v)'), + (0x24B2, '3', '(w)'), + (0x24B3, '3', '(x)'), + (0x24B4, '3', '(y)'), + (0x24B5, '3', '(z)'), + (0x24B6, 'M', 'a'), + (0x24B7, 'M', 'b'), + (0x24B8, 'M', 'c'), + (0x24B9, 'M', 'd'), + (0x24BA, 'M', 'e'), + (0x24BB, 'M', 'f'), + (0x24BC, 'M', 'g'), + ] + +def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x24BD, 'M', 'h'), + (0x24BE, 'M', 'i'), + (0x24BF, 'M', 'j'), + (0x24C0, 'M', 'k'), + (0x24C1, 'M', 'l'), + (0x24C2, 'M', 'm'), + (0x24C3, 'M', 'n'), + (0x24C4, 'M', 'o'), + (0x24C5, 'M', 'p'), + (0x24C6, 'M', 'q'), + (0x24C7, 'M', 'r'), + (0x24C8, 'M', 's'), + (0x24C9, 'M', 't'), + (0x24CA, 'M', 'u'), + (0x24CB, 'M', 'v'), + (0x24CC, 'M', 'w'), + (0x24CD, 'M', 'x'), + (0x24CE, 'M', 'y'), + (0x24CF, 'M', 'z'), + (0x24D0, 'M', 'a'), + (0x24D1, 'M', 'b'), + (0x24D2, 'M', 'c'), + (0x24D3, 'M', 'd'), + (0x24D4, 'M', 'e'), + (0x24D5, 'M', 'f'), + (0x24D6, 'M', 'g'), + (0x24D7, 'M', 'h'), + (0x24D8, 'M', 'i'), + (0x24D9, 'M', 'j'), + (0x24DA, 'M', 'k'), + (0x24DB, 'M', 'l'), + (0x24DC, 'M', 'm'), + (0x24DD, 'M', 'n'), + (0x24DE, 'M', 'o'), + (0x24DF, 'M', 'p'), + (0x24E0, 'M', 'q'), + (0x24E1, 'M', 'r'), + (0x24E2, 'M', 's'), + (0x24E3, 'M', 't'), + (0x24E4, 'M', 'u'), + (0x24E5, 'M', 'v'), + (0x24E6, 'M', 'w'), + (0x24E7, 'M', 'x'), + (0x24E8, 'M', 'y'), + (0x24E9, 'M', 'z'), + (0x24EA, 'M', '0'), + (0x24EB, 'V'), + (0x2A0C, 'M', '∫∫∫∫'), + (0x2A0D, 'V'), + (0x2A74, '3', '::='), + (0x2A75, '3', '=='), + (0x2A76, '3', '==='), + (0x2A77, 'V'), + (0x2ADC, 'M', '⫝̸'), + (0x2ADD, 'V'), + (0x2B74, 'X'), + (0x2B76, 'V'), + (0x2B96, 'X'), + (0x2B97, 'V'), + (0x2C00, 'M', 'ⰰ'), + (0x2C01, 'M', 'ⰱ'), + (0x2C02, 'M', 'ⰲ'), + (0x2C03, 'M', 'ⰳ'), + (0x2C04, 'M', 'ⰴ'), + (0x2C05, 'M', 'ⰵ'), + (0x2C06, 'M', 'ⰶ'), + (0x2C07, 'M', 'ⰷ'), + (0x2C08, 'M', 'ⰸ'), + (0x2C09, 'M', 'ⰹ'), + (0x2C0A, 'M', 'ⰺ'), + (0x2C0B, 'M', 'ⰻ'), + (0x2C0C, 'M', 'ⰼ'), + (0x2C0D, 'M', 'ⰽ'), + (0x2C0E, 'M', 'ⰾ'), + (0x2C0F, 'M', 'ⰿ'), + (0x2C10, 'M', 'ⱀ'), + (0x2C11, 'M', 'ⱁ'), + (0x2C12, 'M', 'ⱂ'), + (0x2C13, 'M', 'ⱃ'), + (0x2C14, 'M', 'ⱄ'), + (0x2C15, 'M', 'ⱅ'), + (0x2C16, 'M', 'ⱆ'), + (0x2C17, 'M', 'ⱇ'), + (0x2C18, 'M', 'ⱈ'), + (0x2C19, 'M', 'ⱉ'), + (0x2C1A, 'M', 'ⱊ'), + (0x2C1B, 'M', 'ⱋ'), + (0x2C1C, 'M', 'ⱌ'), + (0x2C1D, 'M', 'ⱍ'), + (0x2C1E, 'M', 'ⱎ'), + (0x2C1F, 'M', 'ⱏ'), + (0x2C20, 'M', 'ⱐ'), + (0x2C21, 'M', 'ⱑ'), + (0x2C22, 'M', 'ⱒ'), + (0x2C23, 'M', 'ⱓ'), + (0x2C24, 'M', 'ⱔ'), + (0x2C25, 'M', 'ⱕ'), + (0x2C26, 'M', 'ⱖ'), + (0x2C27, 'M', 'ⱗ'), + (0x2C28, 'M', 'ⱘ'), + ] + +def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2C29, 'M', 'ⱙ'), + (0x2C2A, 'M', 'ⱚ'), + (0x2C2B, 'M', 'ⱛ'), + (0x2C2C, 'M', 'ⱜ'), + (0x2C2D, 'M', 'ⱝ'), + (0x2C2E, 'M', 'ⱞ'), + (0x2C2F, 'M', 'ⱟ'), + (0x2C30, 'V'), + (0x2C60, 'M', 'ⱡ'), + (0x2C61, 'V'), + (0x2C62, 'M', 'ɫ'), + (0x2C63, 'M', 'ᵽ'), + (0x2C64, 'M', 'ɽ'), + (0x2C65, 'V'), + (0x2C67, 'M', 'ⱨ'), + (0x2C68, 'V'), + (0x2C69, 'M', 'ⱪ'), + (0x2C6A, 'V'), + (0x2C6B, 'M', 'ⱬ'), + (0x2C6C, 'V'), + (0x2C6D, 'M', 'ɑ'), + (0x2C6E, 'M', 'ɱ'), + (0x2C6F, 'M', 'ɐ'), + (0x2C70, 'M', 'ɒ'), + (0x2C71, 'V'), + (0x2C72, 'M', 'ⱳ'), + (0x2C73, 'V'), + (0x2C75, 'M', 'ⱶ'), + (0x2C76, 'V'), + (0x2C7C, 'M', 'j'), + (0x2C7D, 'M', 'v'), + (0x2C7E, 'M', 'ȿ'), + (0x2C7F, 'M', 'ɀ'), + (0x2C80, 'M', 'ⲁ'), + (0x2C81, 'V'), + (0x2C82, 'M', 'ⲃ'), + (0x2C83, 'V'), + (0x2C84, 'M', 'ⲅ'), + (0x2C85, 'V'), + (0x2C86, 'M', 'ⲇ'), + (0x2C87, 'V'), + (0x2C88, 'M', 'ⲉ'), + (0x2C89, 'V'), + (0x2C8A, 'M', 'ⲋ'), + (0x2C8B, 'V'), + (0x2C8C, 'M', 'ⲍ'), + (0x2C8D, 'V'), + (0x2C8E, 'M', 'ⲏ'), + (0x2C8F, 'V'), + (0x2C90, 'M', 'ⲑ'), + (0x2C91, 'V'), + (0x2C92, 'M', 'ⲓ'), + (0x2C93, 'V'), + (0x2C94, 'M', 'ⲕ'), + (0x2C95, 'V'), + (0x2C96, 'M', 'ⲗ'), + (0x2C97, 'V'), + (0x2C98, 'M', 'ⲙ'), + (0x2C99, 'V'), + (0x2C9A, 'M', 'ⲛ'), + (0x2C9B, 'V'), + (0x2C9C, 'M', 'ⲝ'), + (0x2C9D, 'V'), + (0x2C9E, 'M', 'ⲟ'), + (0x2C9F, 'V'), + (0x2CA0, 'M', 'ⲡ'), + (0x2CA1, 'V'), + (0x2CA2, 'M', 'ⲣ'), + (0x2CA3, 'V'), + (0x2CA4, 'M', 'ⲥ'), + (0x2CA5, 'V'), + (0x2CA6, 'M', 'ⲧ'), + (0x2CA7, 'V'), + (0x2CA8, 'M', 'ⲩ'), + (0x2CA9, 'V'), + (0x2CAA, 'M', 'ⲫ'), + (0x2CAB, 'V'), + (0x2CAC, 'M', 'ⲭ'), + (0x2CAD, 'V'), + (0x2CAE, 'M', 'ⲯ'), + (0x2CAF, 'V'), + (0x2CB0, 'M', 'ⲱ'), + (0x2CB1, 'V'), + (0x2CB2, 'M', 'ⲳ'), + (0x2CB3, 'V'), + (0x2CB4, 'M', 'ⲵ'), + (0x2CB5, 'V'), + (0x2CB6, 'M', 'ⲷ'), + (0x2CB7, 'V'), + (0x2CB8, 'M', 'ⲹ'), + (0x2CB9, 'V'), + (0x2CBA, 'M', 'ⲻ'), + (0x2CBB, 'V'), + (0x2CBC, 'M', 'ⲽ'), + (0x2CBD, 'V'), + (0x2CBE, 'M', 'ⲿ'), + (0x2CBF, 'V'), + (0x2CC0, 'M', 'ⳁ'), + (0x2CC1, 'V'), + (0x2CC2, 'M', 'ⳃ'), + ] + +def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2CC3, 'V'), + (0x2CC4, 'M', 'ⳅ'), + (0x2CC5, 'V'), + (0x2CC6, 'M', 'ⳇ'), + (0x2CC7, 'V'), + (0x2CC8, 'M', 'ⳉ'), + (0x2CC9, 'V'), + (0x2CCA, 'M', 'ⳋ'), + (0x2CCB, 'V'), + (0x2CCC, 'M', 'ⳍ'), + (0x2CCD, 'V'), + (0x2CCE, 'M', 'ⳏ'), + (0x2CCF, 'V'), + (0x2CD0, 'M', 'ⳑ'), + (0x2CD1, 'V'), + (0x2CD2, 'M', 'ⳓ'), + (0x2CD3, 'V'), + (0x2CD4, 'M', 'ⳕ'), + (0x2CD5, 'V'), + (0x2CD6, 'M', 'ⳗ'), + (0x2CD7, 'V'), + (0x2CD8, 'M', 'ⳙ'), + (0x2CD9, 'V'), + (0x2CDA, 'M', 'ⳛ'), + (0x2CDB, 'V'), + (0x2CDC, 'M', 'ⳝ'), + (0x2CDD, 'V'), + (0x2CDE, 'M', 'ⳟ'), + (0x2CDF, 'V'), + (0x2CE0, 'M', 'ⳡ'), + (0x2CE1, 'V'), + (0x2CE2, 'M', 'ⳣ'), + (0x2CE3, 'V'), + (0x2CEB, 'M', 'ⳬ'), + (0x2CEC, 'V'), + (0x2CED, 'M', 'ⳮ'), + (0x2CEE, 'V'), + (0x2CF2, 'M', 'ⳳ'), + (0x2CF3, 'V'), + (0x2CF4, 'X'), + (0x2CF9, 'V'), + (0x2D26, 'X'), + (0x2D27, 'V'), + (0x2D28, 'X'), + (0x2D2D, 'V'), + (0x2D2E, 'X'), + (0x2D30, 'V'), + (0x2D68, 'X'), + (0x2D6F, 'M', 'ⵡ'), + (0x2D70, 'V'), + (0x2D71, 'X'), + (0x2D7F, 'V'), + (0x2D97, 'X'), + (0x2DA0, 'V'), + (0x2DA7, 'X'), + (0x2DA8, 'V'), + (0x2DAF, 'X'), + (0x2DB0, 'V'), + (0x2DB7, 'X'), + (0x2DB8, 'V'), + (0x2DBF, 'X'), + (0x2DC0, 'V'), + (0x2DC7, 'X'), + (0x2DC8, 'V'), + (0x2DCF, 'X'), + (0x2DD0, 'V'), + (0x2DD7, 'X'), + (0x2DD8, 'V'), + (0x2DDF, 'X'), + (0x2DE0, 'V'), + (0x2E5E, 'X'), + (0x2E80, 'V'), + (0x2E9A, 'X'), + (0x2E9B, 'V'), + (0x2E9F, 'M', '母'), + (0x2EA0, 'V'), + (0x2EF3, 'M', '龟'), + (0x2EF4, 'X'), + (0x2F00, 'M', '一'), + (0x2F01, 'M', '丨'), + (0x2F02, 'M', '丶'), + (0x2F03, 'M', '丿'), + (0x2F04, 'M', '乙'), + (0x2F05, 'M', '亅'), + (0x2F06, 'M', '二'), + (0x2F07, 'M', '亠'), + (0x2F08, 'M', '人'), + (0x2F09, 'M', '儿'), + (0x2F0A, 'M', '入'), + (0x2F0B, 'M', '八'), + (0x2F0C, 'M', '冂'), + (0x2F0D, 'M', '冖'), + (0x2F0E, 'M', '冫'), + (0x2F0F, 'M', '几'), + (0x2F10, 'M', '凵'), + (0x2F11, 'M', '刀'), + (0x2F12, 'M', '力'), + (0x2F13, 'M', '勹'), + (0x2F14, 'M', '匕'), + (0x2F15, 'M', '匚'), + ] + +def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F16, 'M', '匸'), + (0x2F17, 'M', '十'), + (0x2F18, 'M', '卜'), + (0x2F19, 'M', '卩'), + (0x2F1A, 'M', '厂'), + (0x2F1B, 'M', '厶'), + (0x2F1C, 'M', '又'), + (0x2F1D, 'M', '口'), + (0x2F1E, 'M', '囗'), + (0x2F1F, 'M', '土'), + (0x2F20, 'M', '士'), + (0x2F21, 'M', '夂'), + (0x2F22, 'M', '夊'), + (0x2F23, 'M', '夕'), + (0x2F24, 'M', '大'), + (0x2F25, 'M', '女'), + (0x2F26, 'M', '子'), + (0x2F27, 'M', '宀'), + (0x2F28, 'M', '寸'), + (0x2F29, 'M', '小'), + (0x2F2A, 'M', '尢'), + (0x2F2B, 'M', '尸'), + (0x2F2C, 'M', '屮'), + (0x2F2D, 'M', '山'), + (0x2F2E, 'M', '巛'), + (0x2F2F, 'M', '工'), + (0x2F30, 'M', '己'), + (0x2F31, 'M', '巾'), + (0x2F32, 'M', '干'), + (0x2F33, 'M', '幺'), + (0x2F34, 'M', '广'), + (0x2F35, 'M', '廴'), + (0x2F36, 'M', '廾'), + (0x2F37, 'M', '弋'), + (0x2F38, 'M', '弓'), + (0x2F39, 'M', '彐'), + (0x2F3A, 'M', '彡'), + (0x2F3B, 'M', '彳'), + (0x2F3C, 'M', '心'), + (0x2F3D, 'M', '戈'), + (0x2F3E, 'M', '戶'), + (0x2F3F, 'M', '手'), + (0x2F40, 'M', '支'), + (0x2F41, 'M', '攴'), + (0x2F42, 'M', '文'), + (0x2F43, 'M', '斗'), + (0x2F44, 'M', '斤'), + (0x2F45, 'M', '方'), + (0x2F46, 'M', '无'), + (0x2F47, 'M', '日'), + (0x2F48, 'M', '曰'), + (0x2F49, 'M', '月'), + (0x2F4A, 'M', '木'), + (0x2F4B, 'M', '欠'), + (0x2F4C, 'M', '止'), + (0x2F4D, 'M', '歹'), + (0x2F4E, 'M', '殳'), + (0x2F4F, 'M', '毋'), + (0x2F50, 'M', '比'), + (0x2F51, 'M', '毛'), + (0x2F52, 'M', '氏'), + (0x2F53, 'M', '气'), + (0x2F54, 'M', '水'), + (0x2F55, 'M', '火'), + (0x2F56, 'M', '爪'), + (0x2F57, 'M', '父'), + (0x2F58, 'M', '爻'), + (0x2F59, 'M', '爿'), + (0x2F5A, 'M', '片'), + (0x2F5B, 'M', '牙'), + (0x2F5C, 'M', '牛'), + (0x2F5D, 'M', '犬'), + (0x2F5E, 'M', '玄'), + (0x2F5F, 'M', '玉'), + (0x2F60, 'M', '瓜'), + (0x2F61, 'M', '瓦'), + (0x2F62, 'M', '甘'), + (0x2F63, 'M', '生'), + (0x2F64, 'M', '用'), + (0x2F65, 'M', '田'), + (0x2F66, 'M', '疋'), + (0x2F67, 'M', '疒'), + (0x2F68, 'M', '癶'), + (0x2F69, 'M', '白'), + (0x2F6A, 'M', '皮'), + (0x2F6B, 'M', '皿'), + (0x2F6C, 'M', '目'), + (0x2F6D, 'M', '矛'), + (0x2F6E, 'M', '矢'), + (0x2F6F, 'M', '石'), + (0x2F70, 'M', '示'), + (0x2F71, 'M', '禸'), + (0x2F72, 'M', '禾'), + (0x2F73, 'M', '穴'), + (0x2F74, 'M', '立'), + (0x2F75, 'M', '竹'), + (0x2F76, 'M', '米'), + (0x2F77, 'M', '糸'), + (0x2F78, 'M', '缶'), + (0x2F79, 'M', '网'), + ] + +def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F7A, 'M', '羊'), + (0x2F7B, 'M', '羽'), + (0x2F7C, 'M', '老'), + (0x2F7D, 'M', '而'), + (0x2F7E, 'M', '耒'), + (0x2F7F, 'M', '耳'), + (0x2F80, 'M', '聿'), + (0x2F81, 'M', '肉'), + (0x2F82, 'M', '臣'), + (0x2F83, 'M', '自'), + (0x2F84, 'M', '至'), + (0x2F85, 'M', '臼'), + (0x2F86, 'M', '舌'), + (0x2F87, 'M', '舛'), + (0x2F88, 'M', '舟'), + (0x2F89, 'M', '艮'), + (0x2F8A, 'M', '色'), + (0x2F8B, 'M', '艸'), + (0x2F8C, 'M', '虍'), + (0x2F8D, 'M', '虫'), + (0x2F8E, 'M', '血'), + (0x2F8F, 'M', '行'), + (0x2F90, 'M', '衣'), + (0x2F91, 'M', '襾'), + (0x2F92, 'M', '見'), + (0x2F93, 'M', '角'), + (0x2F94, 'M', '言'), + (0x2F95, 'M', '谷'), + (0x2F96, 'M', '豆'), + (0x2F97, 'M', '豕'), + (0x2F98, 'M', '豸'), + (0x2F99, 'M', '貝'), + (0x2F9A, 'M', '赤'), + (0x2F9B, 'M', '走'), + (0x2F9C, 'M', '足'), + (0x2F9D, 'M', '身'), + (0x2F9E, 'M', '車'), + (0x2F9F, 'M', '辛'), + (0x2FA0, 'M', '辰'), + (0x2FA1, 'M', '辵'), + (0x2FA2, 'M', '邑'), + (0x2FA3, 'M', '酉'), + (0x2FA4, 'M', '釆'), + (0x2FA5, 'M', '里'), + (0x2FA6, 'M', '金'), + (0x2FA7, 'M', '長'), + (0x2FA8, 'M', '門'), + (0x2FA9, 'M', '阜'), + (0x2FAA, 'M', '隶'), + (0x2FAB, 'M', '隹'), + (0x2FAC, 'M', '雨'), + (0x2FAD, 'M', '靑'), + (0x2FAE, 'M', '非'), + (0x2FAF, 'M', '面'), + (0x2FB0, 'M', '革'), + (0x2FB1, 'M', '韋'), + (0x2FB2, 'M', '韭'), + (0x2FB3, 'M', '音'), + (0x2FB4, 'M', '頁'), + (0x2FB5, 'M', '風'), + (0x2FB6, 'M', '飛'), + (0x2FB7, 'M', '食'), + (0x2FB8, 'M', '首'), + (0x2FB9, 'M', '香'), + (0x2FBA, 'M', '馬'), + (0x2FBB, 'M', '骨'), + (0x2FBC, 'M', '高'), + (0x2FBD, 'M', '髟'), + (0x2FBE, 'M', '鬥'), + (0x2FBF, 'M', '鬯'), + (0x2FC0, 'M', '鬲'), + (0x2FC1, 'M', '鬼'), + (0x2FC2, 'M', '魚'), + (0x2FC3, 'M', '鳥'), + (0x2FC4, 'M', '鹵'), + (0x2FC5, 'M', '鹿'), + (0x2FC6, 'M', '麥'), + (0x2FC7, 'M', '麻'), + (0x2FC8, 'M', '黃'), + (0x2FC9, 'M', '黍'), + (0x2FCA, 'M', '黑'), + (0x2FCB, 'M', '黹'), + (0x2FCC, 'M', '黽'), + (0x2FCD, 'M', '鼎'), + (0x2FCE, 'M', '鼓'), + (0x2FCF, 'M', '鼠'), + (0x2FD0, 'M', '鼻'), + (0x2FD1, 'M', '齊'), + (0x2FD2, 'M', '齒'), + (0x2FD3, 'M', '龍'), + (0x2FD4, 'M', '龜'), + (0x2FD5, 'M', '龠'), + (0x2FD6, 'X'), + (0x3000, '3', ' '), + (0x3001, 'V'), + (0x3002, 'M', '.'), + (0x3003, 'V'), + (0x3036, 'M', '〒'), + (0x3037, 'V'), + (0x3038, 'M', '十'), + ] + +def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3039, 'M', '卄'), + (0x303A, 'M', '卅'), + (0x303B, 'V'), + (0x3040, 'X'), + (0x3041, 'V'), + (0x3097, 'X'), + (0x3099, 'V'), + (0x309B, '3', ' ゙'), + (0x309C, '3', ' ゚'), + (0x309D, 'V'), + (0x309F, 'M', 'より'), + (0x30A0, 'V'), + (0x30FF, 'M', 'コト'), + (0x3100, 'X'), + (0x3105, 'V'), + (0x3130, 'X'), + (0x3131, 'M', 'ᄀ'), + (0x3132, 'M', 'ᄁ'), + (0x3133, 'M', 'ᆪ'), + (0x3134, 'M', 'ᄂ'), + (0x3135, 'M', 'ᆬ'), + (0x3136, 'M', 'ᆭ'), + (0x3137, 'M', 'ᄃ'), + (0x3138, 'M', 'ᄄ'), + (0x3139, 'M', 'ᄅ'), + (0x313A, 'M', 'ᆰ'), + (0x313B, 'M', 'ᆱ'), + (0x313C, 'M', 'ᆲ'), + (0x313D, 'M', 'ᆳ'), + (0x313E, 'M', 'ᆴ'), + (0x313F, 'M', 'ᆵ'), + (0x3140, 'M', 'ᄚ'), + (0x3141, 'M', 'ᄆ'), + (0x3142, 'M', 'ᄇ'), + (0x3143, 'M', 'ᄈ'), + (0x3144, 'M', 'ᄡ'), + (0x3145, 'M', 'ᄉ'), + (0x3146, 'M', 'ᄊ'), + (0x3147, 'M', 'ᄋ'), + (0x3148, 'M', 'ᄌ'), + (0x3149, 'M', 'ᄍ'), + (0x314A, 'M', 'ᄎ'), + (0x314B, 'M', 'ᄏ'), + (0x314C, 'M', 'ᄐ'), + (0x314D, 'M', 'ᄑ'), + (0x314E, 'M', 'ᄒ'), + (0x314F, 'M', 'ᅡ'), + (0x3150, 'M', 'ᅢ'), + (0x3151, 'M', 'ᅣ'), + (0x3152, 'M', 'ᅤ'), + (0x3153, 'M', 'ᅥ'), + (0x3154, 'M', 'ᅦ'), + (0x3155, 'M', 'ᅧ'), + (0x3156, 'M', 'ᅨ'), + (0x3157, 'M', 'ᅩ'), + (0x3158, 'M', 'ᅪ'), + (0x3159, 'M', 'ᅫ'), + (0x315A, 'M', 'ᅬ'), + (0x315B, 'M', 'ᅭ'), + (0x315C, 'M', 'ᅮ'), + (0x315D, 'M', 'ᅯ'), + (0x315E, 'M', 'ᅰ'), + (0x315F, 'M', 'ᅱ'), + (0x3160, 'M', 'ᅲ'), + (0x3161, 'M', 'ᅳ'), + (0x3162, 'M', 'ᅴ'), + (0x3163, 'M', 'ᅵ'), + (0x3164, 'X'), + (0x3165, 'M', 'ᄔ'), + (0x3166, 'M', 'ᄕ'), + (0x3167, 'M', 'ᇇ'), + (0x3168, 'M', 'ᇈ'), + (0x3169, 'M', 'ᇌ'), + (0x316A, 'M', 'ᇎ'), + (0x316B, 'M', 'ᇓ'), + (0x316C, 'M', 'ᇗ'), + (0x316D, 'M', 'ᇙ'), + (0x316E, 'M', 'ᄜ'), + (0x316F, 'M', 'ᇝ'), + (0x3170, 'M', 'ᇟ'), + (0x3171, 'M', 'ᄝ'), + (0x3172, 'M', 'ᄞ'), + (0x3173, 'M', 'ᄠ'), + (0x3174, 'M', 'ᄢ'), + (0x3175, 'M', 'ᄣ'), + (0x3176, 'M', 'ᄧ'), + (0x3177, 'M', 'ᄩ'), + (0x3178, 'M', 'ᄫ'), + (0x3179, 'M', 'ᄬ'), + (0x317A, 'M', 'ᄭ'), + (0x317B, 'M', 'ᄮ'), + (0x317C, 'M', 'ᄯ'), + (0x317D, 'M', 'ᄲ'), + (0x317E, 'M', 'ᄶ'), + (0x317F, 'M', 'ᅀ'), + (0x3180, 'M', 'ᅇ'), + (0x3181, 'M', 'ᅌ'), + (0x3182, 'M', 'ᇱ'), + (0x3183, 'M', 'ᇲ'), + (0x3184, 'M', 'ᅗ'), + ] + +def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3185, 'M', 'ᅘ'), + (0x3186, 'M', 'ᅙ'), + (0x3187, 'M', 'ᆄ'), + (0x3188, 'M', 'ᆅ'), + (0x3189, 'M', 'ᆈ'), + (0x318A, 'M', 'ᆑ'), + (0x318B, 'M', 'ᆒ'), + (0x318C, 'M', 'ᆔ'), + (0x318D, 'M', 'ᆞ'), + (0x318E, 'M', 'ᆡ'), + (0x318F, 'X'), + (0x3190, 'V'), + (0x3192, 'M', '一'), + (0x3193, 'M', '二'), + (0x3194, 'M', '三'), + (0x3195, 'M', '四'), + (0x3196, 'M', '上'), + (0x3197, 'M', '中'), + (0x3198, 'M', '下'), + (0x3199, 'M', '甲'), + (0x319A, 'M', '乙'), + (0x319B, 'M', '丙'), + (0x319C, 'M', '丁'), + (0x319D, 'M', '天'), + (0x319E, 'M', '地'), + (0x319F, 'M', '人'), + (0x31A0, 'V'), + (0x31E4, 'X'), + (0x31F0, 'V'), + (0x3200, '3', '(ᄀ)'), + (0x3201, '3', '(ᄂ)'), + (0x3202, '3', '(ᄃ)'), + (0x3203, '3', '(ᄅ)'), + (0x3204, '3', '(ᄆ)'), + (0x3205, '3', '(ᄇ)'), + (0x3206, '3', '(ᄉ)'), + (0x3207, '3', '(ᄋ)'), + (0x3208, '3', '(ᄌ)'), + (0x3209, '3', '(ᄎ)'), + (0x320A, '3', '(ᄏ)'), + (0x320B, '3', '(ᄐ)'), + (0x320C, '3', '(ᄑ)'), + (0x320D, '3', '(ᄒ)'), + (0x320E, '3', '(가)'), + (0x320F, '3', '(나)'), + (0x3210, '3', '(다)'), + (0x3211, '3', '(라)'), + (0x3212, '3', '(마)'), + (0x3213, '3', '(바)'), + (0x3214, '3', '(사)'), + (0x3215, '3', '(아)'), + (0x3216, '3', '(자)'), + (0x3217, '3', '(차)'), + (0x3218, '3', '(카)'), + (0x3219, '3', '(타)'), + (0x321A, '3', '(파)'), + (0x321B, '3', '(하)'), + (0x321C, '3', '(주)'), + (0x321D, '3', '(오전)'), + (0x321E, '3', '(오후)'), + (0x321F, 'X'), + (0x3220, '3', '(一)'), + (0x3221, '3', '(二)'), + (0x3222, '3', '(三)'), + (0x3223, '3', '(四)'), + (0x3224, '3', '(五)'), + (0x3225, '3', '(六)'), + (0x3226, '3', '(七)'), + (0x3227, '3', '(八)'), + (0x3228, '3', '(九)'), + (0x3229, '3', '(十)'), + (0x322A, '3', '(月)'), + (0x322B, '3', '(火)'), + (0x322C, '3', '(水)'), + (0x322D, '3', '(木)'), + (0x322E, '3', '(金)'), + (0x322F, '3', '(土)'), + (0x3230, '3', '(日)'), + (0x3231, '3', '(株)'), + (0x3232, '3', '(有)'), + (0x3233, '3', '(社)'), + (0x3234, '3', '(名)'), + (0x3235, '3', '(特)'), + (0x3236, '3', '(財)'), + (0x3237, '3', '(祝)'), + (0x3238, '3', '(労)'), + (0x3239, '3', '(代)'), + (0x323A, '3', '(呼)'), + (0x323B, '3', '(学)'), + (0x323C, '3', '(監)'), + (0x323D, '3', '(企)'), + (0x323E, '3', '(資)'), + (0x323F, '3', '(協)'), + (0x3240, '3', '(祭)'), + (0x3241, '3', '(休)'), + (0x3242, '3', '(自)'), + (0x3243, '3', '(至)'), + (0x3244, 'M', '問'), + (0x3245, 'M', '幼'), + (0x3246, 'M', '文'), + ] + +def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3247, 'M', '箏'), + (0x3248, 'V'), + (0x3250, 'M', 'pte'), + (0x3251, 'M', '21'), + (0x3252, 'M', '22'), + (0x3253, 'M', '23'), + (0x3254, 'M', '24'), + (0x3255, 'M', '25'), + (0x3256, 'M', '26'), + (0x3257, 'M', '27'), + (0x3258, 'M', '28'), + (0x3259, 'M', '29'), + (0x325A, 'M', '30'), + (0x325B, 'M', '31'), + (0x325C, 'M', '32'), + (0x325D, 'M', '33'), + (0x325E, 'M', '34'), + (0x325F, 'M', '35'), + (0x3260, 'M', 'ᄀ'), + (0x3261, 'M', 'ᄂ'), + (0x3262, 'M', 'ᄃ'), + (0x3263, 'M', 'ᄅ'), + (0x3264, 'M', 'ᄆ'), + (0x3265, 'M', 'ᄇ'), + (0x3266, 'M', 'ᄉ'), + (0x3267, 'M', 'ᄋ'), + (0x3268, 'M', 'ᄌ'), + (0x3269, 'M', 'ᄎ'), + (0x326A, 'M', 'ᄏ'), + (0x326B, 'M', 'ᄐ'), + (0x326C, 'M', 'ᄑ'), + (0x326D, 'M', 'ᄒ'), + (0x326E, 'M', '가'), + (0x326F, 'M', '나'), + (0x3270, 'M', '다'), + (0x3271, 'M', '라'), + (0x3272, 'M', '마'), + (0x3273, 'M', '바'), + (0x3274, 'M', '사'), + (0x3275, 'M', '아'), + (0x3276, 'M', '자'), + (0x3277, 'M', '차'), + (0x3278, 'M', '카'), + (0x3279, 'M', '타'), + (0x327A, 'M', '파'), + (0x327B, 'M', '하'), + (0x327C, 'M', '참고'), + (0x327D, 'M', '주의'), + (0x327E, 'M', '우'), + (0x327F, 'V'), + (0x3280, 'M', '一'), + (0x3281, 'M', '二'), + (0x3282, 'M', '三'), + (0x3283, 'M', '四'), + (0x3284, 'M', '五'), + (0x3285, 'M', '六'), + (0x3286, 'M', '七'), + (0x3287, 'M', '八'), + (0x3288, 'M', '九'), + (0x3289, 'M', '十'), + (0x328A, 'M', '月'), + (0x328B, 'M', '火'), + (0x328C, 'M', '水'), + (0x328D, 'M', '木'), + (0x328E, 'M', '金'), + (0x328F, 'M', '土'), + (0x3290, 'M', '日'), + (0x3291, 'M', '株'), + (0x3292, 'M', '有'), + (0x3293, 'M', '社'), + (0x3294, 'M', '名'), + (0x3295, 'M', '特'), + (0x3296, 'M', '財'), + (0x3297, 'M', '祝'), + (0x3298, 'M', '労'), + (0x3299, 'M', '秘'), + (0x329A, 'M', '男'), + (0x329B, 'M', '女'), + (0x329C, 'M', '適'), + (0x329D, 'M', '優'), + (0x329E, 'M', '印'), + (0x329F, 'M', '注'), + (0x32A0, 'M', '項'), + (0x32A1, 'M', '休'), + (0x32A2, 'M', '写'), + (0x32A3, 'M', '正'), + (0x32A4, 'M', '上'), + (0x32A5, 'M', '中'), + (0x32A6, 'M', '下'), + (0x32A7, 'M', '左'), + (0x32A8, 'M', '右'), + (0x32A9, 'M', '医'), + (0x32AA, 'M', '宗'), + (0x32AB, 'M', '学'), + (0x32AC, 'M', '監'), + (0x32AD, 'M', '企'), + (0x32AE, 'M', '資'), + (0x32AF, 'M', '協'), + (0x32B0, 'M', '夜'), + (0x32B1, 'M', '36'), + ] + +def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x32B2, 'M', '37'), + (0x32B3, 'M', '38'), + (0x32B4, 'M', '39'), + (0x32B5, 'M', '40'), + (0x32B6, 'M', '41'), + (0x32B7, 'M', '42'), + (0x32B8, 'M', '43'), + (0x32B9, 'M', '44'), + (0x32BA, 'M', '45'), + (0x32BB, 'M', '46'), + (0x32BC, 'M', '47'), + (0x32BD, 'M', '48'), + (0x32BE, 'M', '49'), + (0x32BF, 'M', '50'), + (0x32C0, 'M', '1月'), + (0x32C1, 'M', '2月'), + (0x32C2, 'M', '3月'), + (0x32C3, 'M', '4月'), + (0x32C4, 'M', '5月'), + (0x32C5, 'M', '6月'), + (0x32C6, 'M', '7月'), + (0x32C7, 'M', '8月'), + (0x32C8, 'M', '9月'), + (0x32C9, 'M', '10月'), + (0x32CA, 'M', '11月'), + (0x32CB, 'M', '12月'), + (0x32CC, 'M', 'hg'), + (0x32CD, 'M', 'erg'), + (0x32CE, 'M', 'ev'), + (0x32CF, 'M', 'ltd'), + (0x32D0, 'M', 'ア'), + (0x32D1, 'M', 'イ'), + (0x32D2, 'M', 'ウ'), + (0x32D3, 'M', 'エ'), + (0x32D4, 'M', 'オ'), + (0x32D5, 'M', 'カ'), + (0x32D6, 'M', 'キ'), + (0x32D7, 'M', 'ク'), + (0x32D8, 'M', 'ケ'), + (0x32D9, 'M', 'コ'), + (0x32DA, 'M', 'サ'), + (0x32DB, 'M', 'シ'), + (0x32DC, 'M', 'ス'), + (0x32DD, 'M', 'セ'), + (0x32DE, 'M', 'ソ'), + (0x32DF, 'M', 'タ'), + (0x32E0, 'M', 'チ'), + (0x32E1, 'M', 'ツ'), + (0x32E2, 'M', 'テ'), + (0x32E3, 'M', 'ト'), + (0x32E4, 'M', 'ナ'), + (0x32E5, 'M', 'ニ'), + (0x32E6, 'M', 'ヌ'), + (0x32E7, 'M', 'ネ'), + (0x32E8, 'M', 'ノ'), + (0x32E9, 'M', 'ハ'), + (0x32EA, 'M', 'ヒ'), + (0x32EB, 'M', 'フ'), + (0x32EC, 'M', 'ヘ'), + (0x32ED, 'M', 'ホ'), + (0x32EE, 'M', 'マ'), + (0x32EF, 'M', 'ミ'), + (0x32F0, 'M', 'ム'), + (0x32F1, 'M', 'メ'), + (0x32F2, 'M', 'モ'), + (0x32F3, 'M', 'ヤ'), + (0x32F4, 'M', 'ユ'), + (0x32F5, 'M', 'ヨ'), + (0x32F6, 'M', 'ラ'), + (0x32F7, 'M', 'リ'), + (0x32F8, 'M', 'ル'), + (0x32F9, 'M', 'レ'), + (0x32FA, 'M', 'ロ'), + (0x32FB, 'M', 'ワ'), + (0x32FC, 'M', 'ヰ'), + (0x32FD, 'M', 'ヱ'), + (0x32FE, 'M', 'ヲ'), + (0x32FF, 'M', '令和'), + (0x3300, 'M', 'アパート'), + (0x3301, 'M', 'アルファ'), + (0x3302, 'M', 'アンペア'), + (0x3303, 'M', 'アール'), + (0x3304, 'M', 'イニング'), + (0x3305, 'M', 'インチ'), + (0x3306, 'M', 'ウォン'), + (0x3307, 'M', 'エスクード'), + (0x3308, 'M', 'エーカー'), + (0x3309, 'M', 'オンス'), + (0x330A, 'M', 'オーム'), + (0x330B, 'M', 'カイリ'), + (0x330C, 'M', 'カラット'), + (0x330D, 'M', 'カロリー'), + (0x330E, 'M', 'ガロン'), + (0x330F, 'M', 'ガンマ'), + (0x3310, 'M', 'ギガ'), + (0x3311, 'M', 'ギニー'), + (0x3312, 'M', 'キュリー'), + (0x3313, 'M', 'ギルダー'), + (0x3314, 'M', 'キロ'), + (0x3315, 'M', 'キログラム'), + ] + +def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3316, 'M', 'キロメートル'), + (0x3317, 'M', 'キロワット'), + (0x3318, 'M', 'グラム'), + (0x3319, 'M', 'グラムトン'), + (0x331A, 'M', 'クルゼイロ'), + (0x331B, 'M', 'クローネ'), + (0x331C, 'M', 'ケース'), + (0x331D, 'M', 'コルナ'), + (0x331E, 'M', 'コーポ'), + (0x331F, 'M', 'サイクル'), + (0x3320, 'M', 'サンチーム'), + (0x3321, 'M', 'シリング'), + (0x3322, 'M', 'センチ'), + (0x3323, 'M', 'セント'), + (0x3324, 'M', 'ダース'), + (0x3325, 'M', 'デシ'), + (0x3326, 'M', 'ドル'), + (0x3327, 'M', 'トン'), + (0x3328, 'M', 'ナノ'), + (0x3329, 'M', 'ノット'), + (0x332A, 'M', 'ハイツ'), + (0x332B, 'M', 'パーセント'), + (0x332C, 'M', 'パーツ'), + (0x332D, 'M', 'バーレル'), + (0x332E, 'M', 'ピアストル'), + (0x332F, 'M', 'ピクル'), + (0x3330, 'M', 'ピコ'), + (0x3331, 'M', 'ビル'), + (0x3332, 'M', 'ファラッド'), + (0x3333, 'M', 'フィート'), + (0x3334, 'M', 'ブッシェル'), + (0x3335, 'M', 'フラン'), + (0x3336, 'M', 'ヘクタール'), + (0x3337, 'M', 'ペソ'), + (0x3338, 'M', 'ペニヒ'), + (0x3339, 'M', 'ヘルツ'), + (0x333A, 'M', 'ペンス'), + (0x333B, 'M', 'ページ'), + (0x333C, 'M', 'ベータ'), + (0x333D, 'M', 'ポイント'), + (0x333E, 'M', 'ボルト'), + (0x333F, 'M', 'ホン'), + (0x3340, 'M', 'ポンド'), + (0x3341, 'M', 'ホール'), + (0x3342, 'M', 'ホーン'), + (0x3343, 'M', 'マイクロ'), + (0x3344, 'M', 'マイル'), + (0x3345, 'M', 'マッハ'), + (0x3346, 'M', 'マルク'), + (0x3347, 'M', 'マンション'), + (0x3348, 'M', 'ミクロン'), + (0x3349, 'M', 'ミリ'), + (0x334A, 'M', 'ミリバール'), + (0x334B, 'M', 'メガ'), + (0x334C, 'M', 'メガトン'), + (0x334D, 'M', 'メートル'), + (0x334E, 'M', 'ヤード'), + (0x334F, 'M', 'ヤール'), + (0x3350, 'M', 'ユアン'), + (0x3351, 'M', 'リットル'), + (0x3352, 'M', 'リラ'), + (0x3353, 'M', 'ルピー'), + (0x3354, 'M', 'ルーブル'), + (0x3355, 'M', 'レム'), + (0x3356, 'M', 'レントゲン'), + (0x3357, 'M', 'ワット'), + (0x3358, 'M', '0点'), + (0x3359, 'M', '1点'), + (0x335A, 'M', '2点'), + (0x335B, 'M', '3点'), + (0x335C, 'M', '4点'), + (0x335D, 'M', '5点'), + (0x335E, 'M', '6点'), + (0x335F, 'M', '7点'), + (0x3360, 'M', '8点'), + (0x3361, 'M', '9点'), + (0x3362, 'M', '10点'), + (0x3363, 'M', '11点'), + (0x3364, 'M', '12点'), + (0x3365, 'M', '13点'), + (0x3366, 'M', '14点'), + (0x3367, 'M', '15点'), + (0x3368, 'M', '16点'), + (0x3369, 'M', '17点'), + (0x336A, 'M', '18点'), + (0x336B, 'M', '19点'), + (0x336C, 'M', '20点'), + (0x336D, 'M', '21点'), + (0x336E, 'M', '22点'), + (0x336F, 'M', '23点'), + (0x3370, 'M', '24点'), + (0x3371, 'M', 'hpa'), + (0x3372, 'M', 'da'), + (0x3373, 'M', 'au'), + (0x3374, 'M', 'bar'), + (0x3375, 'M', 'ov'), + (0x3376, 'M', 'pc'), + (0x3377, 'M', 'dm'), + (0x3378, 'M', 'dm2'), + (0x3379, 'M', 'dm3'), + ] + +def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x337A, 'M', 'iu'), + (0x337B, 'M', '平成'), + (0x337C, 'M', '昭和'), + (0x337D, 'M', '大正'), + (0x337E, 'M', '明治'), + (0x337F, 'M', '株式会社'), + (0x3380, 'M', 'pa'), + (0x3381, 'M', 'na'), + (0x3382, 'M', 'μa'), + (0x3383, 'M', 'ma'), + (0x3384, 'M', 'ka'), + (0x3385, 'M', 'kb'), + (0x3386, 'M', 'mb'), + (0x3387, 'M', 'gb'), + (0x3388, 'M', 'cal'), + (0x3389, 'M', 'kcal'), + (0x338A, 'M', 'pf'), + (0x338B, 'M', 'nf'), + (0x338C, 'M', 'μf'), + (0x338D, 'M', 'μg'), + (0x338E, 'M', 'mg'), + (0x338F, 'M', 'kg'), + (0x3390, 'M', 'hz'), + (0x3391, 'M', 'khz'), + (0x3392, 'M', 'mhz'), + (0x3393, 'M', 'ghz'), + (0x3394, 'M', 'thz'), + (0x3395, 'M', 'μl'), + (0x3396, 'M', 'ml'), + (0x3397, 'M', 'dl'), + (0x3398, 'M', 'kl'), + (0x3399, 'M', 'fm'), + (0x339A, 'M', 'nm'), + (0x339B, 'M', 'μm'), + (0x339C, 'M', 'mm'), + (0x339D, 'M', 'cm'), + (0x339E, 'M', 'km'), + (0x339F, 'M', 'mm2'), + (0x33A0, 'M', 'cm2'), + (0x33A1, 'M', 'm2'), + (0x33A2, 'M', 'km2'), + (0x33A3, 'M', 'mm3'), + (0x33A4, 'M', 'cm3'), + (0x33A5, 'M', 'm3'), + (0x33A6, 'M', 'km3'), + (0x33A7, 'M', 'm∕s'), + (0x33A8, 'M', 'm∕s2'), + (0x33A9, 'M', 'pa'), + (0x33AA, 'M', 'kpa'), + (0x33AB, 'M', 'mpa'), + (0x33AC, 'M', 'gpa'), + (0x33AD, 'M', 'rad'), + (0x33AE, 'M', 'rad∕s'), + (0x33AF, 'M', 'rad∕s2'), + (0x33B0, 'M', 'ps'), + (0x33B1, 'M', 'ns'), + (0x33B2, 'M', 'μs'), + (0x33B3, 'M', 'ms'), + (0x33B4, 'M', 'pv'), + (0x33B5, 'M', 'nv'), + (0x33B6, 'M', 'μv'), + (0x33B7, 'M', 'mv'), + (0x33B8, 'M', 'kv'), + (0x33B9, 'M', 'mv'), + (0x33BA, 'M', 'pw'), + (0x33BB, 'M', 'nw'), + (0x33BC, 'M', 'μw'), + (0x33BD, 'M', 'mw'), + (0x33BE, 'M', 'kw'), + (0x33BF, 'M', 'mw'), + (0x33C0, 'M', 'kω'), + (0x33C1, 'M', 'mω'), + (0x33C2, 'X'), + (0x33C3, 'M', 'bq'), + (0x33C4, 'M', 'cc'), + (0x33C5, 'M', 'cd'), + (0x33C6, 'M', 'c∕kg'), + (0x33C7, 'X'), + (0x33C8, 'M', 'db'), + (0x33C9, 'M', 'gy'), + (0x33CA, 'M', 'ha'), + (0x33CB, 'M', 'hp'), + (0x33CC, 'M', 'in'), + (0x33CD, 'M', 'kk'), + (0x33CE, 'M', 'km'), + (0x33CF, 'M', 'kt'), + (0x33D0, 'M', 'lm'), + (0x33D1, 'M', 'ln'), + (0x33D2, 'M', 'log'), + (0x33D3, 'M', 'lx'), + (0x33D4, 'M', 'mb'), + (0x33D5, 'M', 'mil'), + (0x33D6, 'M', 'mol'), + (0x33D7, 'M', 'ph'), + (0x33D8, 'X'), + (0x33D9, 'M', 'ppm'), + (0x33DA, 'M', 'pr'), + (0x33DB, 'M', 'sr'), + (0x33DC, 'M', 'sv'), + (0x33DD, 'M', 'wb'), + ] + +def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x33DE, 'M', 'v∕m'), + (0x33DF, 'M', 'a∕m'), + (0x33E0, 'M', '1日'), + (0x33E1, 'M', '2日'), + (0x33E2, 'M', '3日'), + (0x33E3, 'M', '4日'), + (0x33E4, 'M', '5日'), + (0x33E5, 'M', '6日'), + (0x33E6, 'M', '7日'), + (0x33E7, 'M', '8日'), + (0x33E8, 'M', '9日'), + (0x33E9, 'M', '10日'), + (0x33EA, 'M', '11日'), + (0x33EB, 'M', '12日'), + (0x33EC, 'M', '13日'), + (0x33ED, 'M', '14日'), + (0x33EE, 'M', '15日'), + (0x33EF, 'M', '16日'), + (0x33F0, 'M', '17日'), + (0x33F1, 'M', '18日'), + (0x33F2, 'M', '19日'), + (0x33F3, 'M', '20日'), + (0x33F4, 'M', '21日'), + (0x33F5, 'M', '22日'), + (0x33F6, 'M', '23日'), + (0x33F7, 'M', '24日'), + (0x33F8, 'M', '25日'), + (0x33F9, 'M', '26日'), + (0x33FA, 'M', '27日'), + (0x33FB, 'M', '28日'), + (0x33FC, 'M', '29日'), + (0x33FD, 'M', '30日'), + (0x33FE, 'M', '31日'), + (0x33FF, 'M', 'gal'), + (0x3400, 'V'), + (0xA48D, 'X'), + (0xA490, 'V'), + (0xA4C7, 'X'), + (0xA4D0, 'V'), + (0xA62C, 'X'), + (0xA640, 'M', 'ꙁ'), + (0xA641, 'V'), + (0xA642, 'M', 'ꙃ'), + (0xA643, 'V'), + (0xA644, 'M', 'ꙅ'), + (0xA645, 'V'), + (0xA646, 'M', 'ꙇ'), + (0xA647, 'V'), + (0xA648, 'M', 'ꙉ'), + (0xA649, 'V'), + (0xA64A, 'M', 'ꙋ'), + (0xA64B, 'V'), + (0xA64C, 'M', 'ꙍ'), + (0xA64D, 'V'), + (0xA64E, 'M', 'ꙏ'), + (0xA64F, 'V'), + (0xA650, 'M', 'ꙑ'), + (0xA651, 'V'), + (0xA652, 'M', 'ꙓ'), + (0xA653, 'V'), + (0xA654, 'M', 'ꙕ'), + (0xA655, 'V'), + (0xA656, 'M', 'ꙗ'), + (0xA657, 'V'), + (0xA658, 'M', 'ꙙ'), + (0xA659, 'V'), + (0xA65A, 'M', 'ꙛ'), + (0xA65B, 'V'), + (0xA65C, 'M', 'ꙝ'), + (0xA65D, 'V'), + (0xA65E, 'M', 'ꙟ'), + (0xA65F, 'V'), + (0xA660, 'M', 'ꙡ'), + (0xA661, 'V'), + (0xA662, 'M', 'ꙣ'), + (0xA663, 'V'), + (0xA664, 'M', 'ꙥ'), + (0xA665, 'V'), + (0xA666, 'M', 'ꙧ'), + (0xA667, 'V'), + (0xA668, 'M', 'ꙩ'), + (0xA669, 'V'), + (0xA66A, 'M', 'ꙫ'), + (0xA66B, 'V'), + (0xA66C, 'M', 'ꙭ'), + (0xA66D, 'V'), + (0xA680, 'M', 'ꚁ'), + (0xA681, 'V'), + (0xA682, 'M', 'ꚃ'), + (0xA683, 'V'), + (0xA684, 'M', 'ꚅ'), + (0xA685, 'V'), + (0xA686, 'M', 'ꚇ'), + (0xA687, 'V'), + (0xA688, 'M', 'ꚉ'), + (0xA689, 'V'), + (0xA68A, 'M', 'ꚋ'), + (0xA68B, 'V'), + (0xA68C, 'M', 'ꚍ'), + (0xA68D, 'V'), + ] + +def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA68E, 'M', 'ꚏ'), + (0xA68F, 'V'), + (0xA690, 'M', 'ꚑ'), + (0xA691, 'V'), + (0xA692, 'M', 'ꚓ'), + (0xA693, 'V'), + (0xA694, 'M', 'ꚕ'), + (0xA695, 'V'), + (0xA696, 'M', 'ꚗ'), + (0xA697, 'V'), + (0xA698, 'M', 'ꚙ'), + (0xA699, 'V'), + (0xA69A, 'M', 'ꚛ'), + (0xA69B, 'V'), + (0xA69C, 'M', 'ъ'), + (0xA69D, 'M', 'ь'), + (0xA69E, 'V'), + (0xA6F8, 'X'), + (0xA700, 'V'), + (0xA722, 'M', 'ꜣ'), + (0xA723, 'V'), + (0xA724, 'M', 'ꜥ'), + (0xA725, 'V'), + (0xA726, 'M', 'ꜧ'), + (0xA727, 'V'), + (0xA728, 'M', 'ꜩ'), + (0xA729, 'V'), + (0xA72A, 'M', 'ꜫ'), + (0xA72B, 'V'), + (0xA72C, 'M', 'ꜭ'), + (0xA72D, 'V'), + (0xA72E, 'M', 'ꜯ'), + (0xA72F, 'V'), + (0xA732, 'M', 'ꜳ'), + (0xA733, 'V'), + (0xA734, 'M', 'ꜵ'), + (0xA735, 'V'), + (0xA736, 'M', 'ꜷ'), + (0xA737, 'V'), + (0xA738, 'M', 'ꜹ'), + (0xA739, 'V'), + (0xA73A, 'M', 'ꜻ'), + (0xA73B, 'V'), + (0xA73C, 'M', 'ꜽ'), + (0xA73D, 'V'), + (0xA73E, 'M', 'ꜿ'), + (0xA73F, 'V'), + (0xA740, 'M', 'ꝁ'), + (0xA741, 'V'), + (0xA742, 'M', 'ꝃ'), + (0xA743, 'V'), + (0xA744, 'M', 'ꝅ'), + (0xA745, 'V'), + (0xA746, 'M', 'ꝇ'), + (0xA747, 'V'), + (0xA748, 'M', 'ꝉ'), + (0xA749, 'V'), + (0xA74A, 'M', 'ꝋ'), + (0xA74B, 'V'), + (0xA74C, 'M', 'ꝍ'), + (0xA74D, 'V'), + (0xA74E, 'M', 'ꝏ'), + (0xA74F, 'V'), + (0xA750, 'M', 'ꝑ'), + (0xA751, 'V'), + (0xA752, 'M', 'ꝓ'), + (0xA753, 'V'), + (0xA754, 'M', 'ꝕ'), + (0xA755, 'V'), + (0xA756, 'M', 'ꝗ'), + (0xA757, 'V'), + (0xA758, 'M', 'ꝙ'), + (0xA759, 'V'), + (0xA75A, 'M', 'ꝛ'), + (0xA75B, 'V'), + (0xA75C, 'M', 'ꝝ'), + (0xA75D, 'V'), + (0xA75E, 'M', 'ꝟ'), + (0xA75F, 'V'), + (0xA760, 'M', 'ꝡ'), + (0xA761, 'V'), + (0xA762, 'M', 'ꝣ'), + (0xA763, 'V'), + (0xA764, 'M', 'ꝥ'), + (0xA765, 'V'), + (0xA766, 'M', 'ꝧ'), + (0xA767, 'V'), + (0xA768, 'M', 'ꝩ'), + (0xA769, 'V'), + (0xA76A, 'M', 'ꝫ'), + (0xA76B, 'V'), + (0xA76C, 'M', 'ꝭ'), + (0xA76D, 'V'), + (0xA76E, 'M', 'ꝯ'), + (0xA76F, 'V'), + (0xA770, 'M', 'ꝯ'), + (0xA771, 'V'), + (0xA779, 'M', 'ꝺ'), + (0xA77A, 'V'), + (0xA77B, 'M', 'ꝼ'), + ] + +def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA77C, 'V'), + (0xA77D, 'M', 'ᵹ'), + (0xA77E, 'M', 'ꝿ'), + (0xA77F, 'V'), + (0xA780, 'M', 'ꞁ'), + (0xA781, 'V'), + (0xA782, 'M', 'ꞃ'), + (0xA783, 'V'), + (0xA784, 'M', 'ꞅ'), + (0xA785, 'V'), + (0xA786, 'M', 'ꞇ'), + (0xA787, 'V'), + (0xA78B, 'M', 'ꞌ'), + (0xA78C, 'V'), + (0xA78D, 'M', 'ɥ'), + (0xA78E, 'V'), + (0xA790, 'M', 'ꞑ'), + (0xA791, 'V'), + (0xA792, 'M', 'ꞓ'), + (0xA793, 'V'), + (0xA796, 'M', 'ꞗ'), + (0xA797, 'V'), + (0xA798, 'M', 'ꞙ'), + (0xA799, 'V'), + (0xA79A, 'M', 'ꞛ'), + (0xA79B, 'V'), + (0xA79C, 'M', 'ꞝ'), + (0xA79D, 'V'), + (0xA79E, 'M', 'ꞟ'), + (0xA79F, 'V'), + (0xA7A0, 'M', 'ꞡ'), + (0xA7A1, 'V'), + (0xA7A2, 'M', 'ꞣ'), + (0xA7A3, 'V'), + (0xA7A4, 'M', 'ꞥ'), + (0xA7A5, 'V'), + (0xA7A6, 'M', 'ꞧ'), + (0xA7A7, 'V'), + (0xA7A8, 'M', 'ꞩ'), + (0xA7A9, 'V'), + (0xA7AA, 'M', 'ɦ'), + (0xA7AB, 'M', 'ɜ'), + (0xA7AC, 'M', 'ɡ'), + (0xA7AD, 'M', 'ɬ'), + (0xA7AE, 'M', 'ɪ'), + (0xA7AF, 'V'), + (0xA7B0, 'M', 'ʞ'), + (0xA7B1, 'M', 'ʇ'), + (0xA7B2, 'M', 'ʝ'), + (0xA7B3, 'M', 'ꭓ'), + (0xA7B4, 'M', 'ꞵ'), + (0xA7B5, 'V'), + (0xA7B6, 'M', 'ꞷ'), + (0xA7B7, 'V'), + (0xA7B8, 'M', 'ꞹ'), + (0xA7B9, 'V'), + (0xA7BA, 'M', 'ꞻ'), + (0xA7BB, 'V'), + (0xA7BC, 'M', 'ꞽ'), + (0xA7BD, 'V'), + (0xA7BE, 'M', 'ꞿ'), + (0xA7BF, 'V'), + (0xA7C0, 'M', 'ꟁ'), + (0xA7C1, 'V'), + (0xA7C2, 'M', 'ꟃ'), + (0xA7C3, 'V'), + (0xA7C4, 'M', 'ꞔ'), + (0xA7C5, 'M', 'ʂ'), + (0xA7C6, 'M', 'ᶎ'), + (0xA7C7, 'M', 'ꟈ'), + (0xA7C8, 'V'), + (0xA7C9, 'M', 'ꟊ'), + (0xA7CA, 'V'), + (0xA7CB, 'X'), + (0xA7D0, 'M', 'ꟑ'), + (0xA7D1, 'V'), + (0xA7D2, 'X'), + (0xA7D3, 'V'), + (0xA7D4, 'X'), + (0xA7D5, 'V'), + (0xA7D6, 'M', 'ꟗ'), + (0xA7D7, 'V'), + (0xA7D8, 'M', 'ꟙ'), + (0xA7D9, 'V'), + (0xA7DA, 'X'), + (0xA7F2, 'M', 'c'), + (0xA7F3, 'M', 'f'), + (0xA7F4, 'M', 'q'), + (0xA7F5, 'M', 'ꟶ'), + (0xA7F6, 'V'), + (0xA7F8, 'M', 'ħ'), + (0xA7F9, 'M', 'œ'), + (0xA7FA, 'V'), + (0xA82D, 'X'), + (0xA830, 'V'), + (0xA83A, 'X'), + (0xA840, 'V'), + (0xA878, 'X'), + (0xA880, 'V'), + (0xA8C6, 'X'), + ] + +def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA8CE, 'V'), + (0xA8DA, 'X'), + (0xA8E0, 'V'), + (0xA954, 'X'), + (0xA95F, 'V'), + (0xA97D, 'X'), + (0xA980, 'V'), + (0xA9CE, 'X'), + (0xA9CF, 'V'), + (0xA9DA, 'X'), + (0xA9DE, 'V'), + (0xA9FF, 'X'), + (0xAA00, 'V'), + (0xAA37, 'X'), + (0xAA40, 'V'), + (0xAA4E, 'X'), + (0xAA50, 'V'), + (0xAA5A, 'X'), + (0xAA5C, 'V'), + (0xAAC3, 'X'), + (0xAADB, 'V'), + (0xAAF7, 'X'), + (0xAB01, 'V'), + (0xAB07, 'X'), + (0xAB09, 'V'), + (0xAB0F, 'X'), + (0xAB11, 'V'), + (0xAB17, 'X'), + (0xAB20, 'V'), + (0xAB27, 'X'), + (0xAB28, 'V'), + (0xAB2F, 'X'), + (0xAB30, 'V'), + (0xAB5C, 'M', 'ꜧ'), + (0xAB5D, 'M', 'ꬷ'), + (0xAB5E, 'M', 'ɫ'), + (0xAB5F, 'M', 'ꭒ'), + (0xAB60, 'V'), + (0xAB69, 'M', 'ʍ'), + (0xAB6A, 'V'), + (0xAB6C, 'X'), + (0xAB70, 'M', 'Ꭰ'), + (0xAB71, 'M', 'Ꭱ'), + (0xAB72, 'M', 'Ꭲ'), + (0xAB73, 'M', 'Ꭳ'), + (0xAB74, 'M', 'Ꭴ'), + (0xAB75, 'M', 'Ꭵ'), + (0xAB76, 'M', 'Ꭶ'), + (0xAB77, 'M', 'Ꭷ'), + (0xAB78, 'M', 'Ꭸ'), + (0xAB79, 'M', 'Ꭹ'), + (0xAB7A, 'M', 'Ꭺ'), + (0xAB7B, 'M', 'Ꭻ'), + (0xAB7C, 'M', 'Ꭼ'), + (0xAB7D, 'M', 'Ꭽ'), + (0xAB7E, 'M', 'Ꭾ'), + (0xAB7F, 'M', 'Ꭿ'), + (0xAB80, 'M', 'Ꮀ'), + (0xAB81, 'M', 'Ꮁ'), + (0xAB82, 'M', 'Ꮂ'), + (0xAB83, 'M', 'Ꮃ'), + (0xAB84, 'M', 'Ꮄ'), + (0xAB85, 'M', 'Ꮅ'), + (0xAB86, 'M', 'Ꮆ'), + (0xAB87, 'M', 'Ꮇ'), + (0xAB88, 'M', 'Ꮈ'), + (0xAB89, 'M', 'Ꮉ'), + (0xAB8A, 'M', 'Ꮊ'), + (0xAB8B, 'M', 'Ꮋ'), + (0xAB8C, 'M', 'Ꮌ'), + (0xAB8D, 'M', 'Ꮍ'), + (0xAB8E, 'M', 'Ꮎ'), + (0xAB8F, 'M', 'Ꮏ'), + (0xAB90, 'M', 'Ꮐ'), + (0xAB91, 'M', 'Ꮑ'), + (0xAB92, 'M', 'Ꮒ'), + (0xAB93, 'M', 'Ꮓ'), + (0xAB94, 'M', 'Ꮔ'), + (0xAB95, 'M', 'Ꮕ'), + (0xAB96, 'M', 'Ꮖ'), + (0xAB97, 'M', 'Ꮗ'), + (0xAB98, 'M', 'Ꮘ'), + (0xAB99, 'M', 'Ꮙ'), + (0xAB9A, 'M', 'Ꮚ'), + (0xAB9B, 'M', 'Ꮛ'), + (0xAB9C, 'M', 'Ꮜ'), + (0xAB9D, 'M', 'Ꮝ'), + (0xAB9E, 'M', 'Ꮞ'), + (0xAB9F, 'M', 'Ꮟ'), + (0xABA0, 'M', 'Ꮠ'), + (0xABA1, 'M', 'Ꮡ'), + (0xABA2, 'M', 'Ꮢ'), + (0xABA3, 'M', 'Ꮣ'), + (0xABA4, 'M', 'Ꮤ'), + (0xABA5, 'M', 'Ꮥ'), + (0xABA6, 'M', 'Ꮦ'), + (0xABA7, 'M', 'Ꮧ'), + (0xABA8, 'M', 'Ꮨ'), + (0xABA9, 'M', 'Ꮩ'), + (0xABAA, 'M', 'Ꮪ'), + ] + +def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xABAB, 'M', 'Ꮫ'), + (0xABAC, 'M', 'Ꮬ'), + (0xABAD, 'M', 'Ꮭ'), + (0xABAE, 'M', 'Ꮮ'), + (0xABAF, 'M', 'Ꮯ'), + (0xABB0, 'M', 'Ꮰ'), + (0xABB1, 'M', 'Ꮱ'), + (0xABB2, 'M', 'Ꮲ'), + (0xABB3, 'M', 'Ꮳ'), + (0xABB4, 'M', 'Ꮴ'), + (0xABB5, 'M', 'Ꮵ'), + (0xABB6, 'M', 'Ꮶ'), + (0xABB7, 'M', 'Ꮷ'), + (0xABB8, 'M', 'Ꮸ'), + (0xABB9, 'M', 'Ꮹ'), + (0xABBA, 'M', 'Ꮺ'), + (0xABBB, 'M', 'Ꮻ'), + (0xABBC, 'M', 'Ꮼ'), + (0xABBD, 'M', 'Ꮽ'), + (0xABBE, 'M', 'Ꮾ'), + (0xABBF, 'M', 'Ꮿ'), + (0xABC0, 'V'), + (0xABEE, 'X'), + (0xABF0, 'V'), + (0xABFA, 'X'), + (0xAC00, 'V'), + (0xD7A4, 'X'), + (0xD7B0, 'V'), + (0xD7C7, 'X'), + (0xD7CB, 'V'), + (0xD7FC, 'X'), + (0xF900, 'M', '豈'), + (0xF901, 'M', '更'), + (0xF902, 'M', '車'), + (0xF903, 'M', '賈'), + (0xF904, 'M', '滑'), + (0xF905, 'M', '串'), + (0xF906, 'M', '句'), + (0xF907, 'M', '龜'), + (0xF909, 'M', '契'), + (0xF90A, 'M', '金'), + (0xF90B, 'M', '喇'), + (0xF90C, 'M', '奈'), + (0xF90D, 'M', '懶'), + (0xF90E, 'M', '癩'), + (0xF90F, 'M', '羅'), + (0xF910, 'M', '蘿'), + (0xF911, 'M', '螺'), + (0xF912, 'M', '裸'), + (0xF913, 'M', '邏'), + (0xF914, 'M', '樂'), + (0xF915, 'M', '洛'), + (0xF916, 'M', '烙'), + (0xF917, 'M', '珞'), + (0xF918, 'M', '落'), + (0xF919, 'M', '酪'), + (0xF91A, 'M', '駱'), + (0xF91B, 'M', '亂'), + (0xF91C, 'M', '卵'), + (0xF91D, 'M', '欄'), + (0xF91E, 'M', '爛'), + (0xF91F, 'M', '蘭'), + (0xF920, 'M', '鸞'), + (0xF921, 'M', '嵐'), + (0xF922, 'M', '濫'), + (0xF923, 'M', '藍'), + (0xF924, 'M', '襤'), + (0xF925, 'M', '拉'), + (0xF926, 'M', '臘'), + (0xF927, 'M', '蠟'), + (0xF928, 'M', '廊'), + (0xF929, 'M', '朗'), + (0xF92A, 'M', '浪'), + (0xF92B, 'M', '狼'), + (0xF92C, 'M', '郎'), + (0xF92D, 'M', '來'), + (0xF92E, 'M', '冷'), + (0xF92F, 'M', '勞'), + (0xF930, 'M', '擄'), + (0xF931, 'M', '櫓'), + (0xF932, 'M', '爐'), + (0xF933, 'M', '盧'), + (0xF934, 'M', '老'), + (0xF935, 'M', '蘆'), + (0xF936, 'M', '虜'), + (0xF937, 'M', '路'), + (0xF938, 'M', '露'), + (0xF939, 'M', '魯'), + (0xF93A, 'M', '鷺'), + (0xF93B, 'M', '碌'), + (0xF93C, 'M', '祿'), + (0xF93D, 'M', '綠'), + (0xF93E, 'M', '菉'), + (0xF93F, 'M', '錄'), + (0xF940, 'M', '鹿'), + (0xF941, 'M', '論'), + (0xF942, 'M', '壟'), + (0xF943, 'M', '弄'), + (0xF944, 'M', '籠'), + (0xF945, 'M', '聾'), + ] + +def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xF946, 'M', '牢'), + (0xF947, 'M', '磊'), + (0xF948, 'M', '賂'), + (0xF949, 'M', '雷'), + (0xF94A, 'M', '壘'), + (0xF94B, 'M', '屢'), + (0xF94C, 'M', '樓'), + (0xF94D, 'M', '淚'), + (0xF94E, 'M', '漏'), + (0xF94F, 'M', '累'), + (0xF950, 'M', '縷'), + (0xF951, 'M', '陋'), + (0xF952, 'M', '勒'), + (0xF953, 'M', '肋'), + (0xF954, 'M', '凜'), + (0xF955, 'M', '凌'), + (0xF956, 'M', '稜'), + (0xF957, 'M', '綾'), + (0xF958, 'M', '菱'), + (0xF959, 'M', '陵'), + (0xF95A, 'M', '讀'), + (0xF95B, 'M', '拏'), + (0xF95C, 'M', '樂'), + (0xF95D, 'M', '諾'), + (0xF95E, 'M', '丹'), + (0xF95F, 'M', '寧'), + (0xF960, 'M', '怒'), + (0xF961, 'M', '率'), + (0xF962, 'M', '異'), + (0xF963, 'M', '北'), + (0xF964, 'M', '磻'), + (0xF965, 'M', '便'), + (0xF966, 'M', '復'), + (0xF967, 'M', '不'), + (0xF968, 'M', '泌'), + (0xF969, 'M', '數'), + (0xF96A, 'M', '索'), + (0xF96B, 'M', '參'), + (0xF96C, 'M', '塞'), + (0xF96D, 'M', '省'), + (0xF96E, 'M', '葉'), + (0xF96F, 'M', '說'), + (0xF970, 'M', '殺'), + (0xF971, 'M', '辰'), + (0xF972, 'M', '沈'), + (0xF973, 'M', '拾'), + (0xF974, 'M', '若'), + (0xF975, 'M', '掠'), + (0xF976, 'M', '略'), + (0xF977, 'M', '亮'), + (0xF978, 'M', '兩'), + (0xF979, 'M', '凉'), + (0xF97A, 'M', '梁'), + (0xF97B, 'M', '糧'), + (0xF97C, 'M', '良'), + (0xF97D, 'M', '諒'), + (0xF97E, 'M', '量'), + (0xF97F, 'M', '勵'), + (0xF980, 'M', '呂'), + (0xF981, 'M', '女'), + (0xF982, 'M', '廬'), + (0xF983, 'M', '旅'), + (0xF984, 'M', '濾'), + (0xF985, 'M', '礪'), + (0xF986, 'M', '閭'), + (0xF987, 'M', '驪'), + (0xF988, 'M', '麗'), + (0xF989, 'M', '黎'), + (0xF98A, 'M', '力'), + (0xF98B, 'M', '曆'), + (0xF98C, 'M', '歷'), + (0xF98D, 'M', '轢'), + (0xF98E, 'M', '年'), + (0xF98F, 'M', '憐'), + (0xF990, 'M', '戀'), + (0xF991, 'M', '撚'), + (0xF992, 'M', '漣'), + (0xF993, 'M', '煉'), + (0xF994, 'M', '璉'), + (0xF995, 'M', '秊'), + (0xF996, 'M', '練'), + (0xF997, 'M', '聯'), + (0xF998, 'M', '輦'), + (0xF999, 'M', '蓮'), + (0xF99A, 'M', '連'), + (0xF99B, 'M', '鍊'), + (0xF99C, 'M', '列'), + (0xF99D, 'M', '劣'), + (0xF99E, 'M', '咽'), + (0xF99F, 'M', '烈'), + (0xF9A0, 'M', '裂'), + (0xF9A1, 'M', '說'), + (0xF9A2, 'M', '廉'), + (0xF9A3, 'M', '念'), + (0xF9A4, 'M', '捻'), + (0xF9A5, 'M', '殮'), + (0xF9A6, 'M', '簾'), + (0xF9A7, 'M', '獵'), + (0xF9A8, 'M', '令'), + (0xF9A9, 'M', '囹'), + ] + +def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xF9AA, 'M', '寧'), + (0xF9AB, 'M', '嶺'), + (0xF9AC, 'M', '怜'), + (0xF9AD, 'M', '玲'), + (0xF9AE, 'M', '瑩'), + (0xF9AF, 'M', '羚'), + (0xF9B0, 'M', '聆'), + (0xF9B1, 'M', '鈴'), + (0xF9B2, 'M', '零'), + (0xF9B3, 'M', '靈'), + (0xF9B4, 'M', '領'), + (0xF9B5, 'M', '例'), + (0xF9B6, 'M', '禮'), + (0xF9B7, 'M', '醴'), + (0xF9B8, 'M', '隸'), + (0xF9B9, 'M', '惡'), + (0xF9BA, 'M', '了'), + (0xF9BB, 'M', '僚'), + (0xF9BC, 'M', '寮'), + (0xF9BD, 'M', '尿'), + (0xF9BE, 'M', '料'), + (0xF9BF, 'M', '樂'), + (0xF9C0, 'M', '燎'), + (0xF9C1, 'M', '療'), + (0xF9C2, 'M', '蓼'), + (0xF9C3, 'M', '遼'), + (0xF9C4, 'M', '龍'), + (0xF9C5, 'M', '暈'), + (0xF9C6, 'M', '阮'), + (0xF9C7, 'M', '劉'), + (0xF9C8, 'M', '杻'), + (0xF9C9, 'M', '柳'), + (0xF9CA, 'M', '流'), + (0xF9CB, 'M', '溜'), + (0xF9CC, 'M', '琉'), + (0xF9CD, 'M', '留'), + (0xF9CE, 'M', '硫'), + (0xF9CF, 'M', '紐'), + (0xF9D0, 'M', '類'), + (0xF9D1, 'M', '六'), + (0xF9D2, 'M', '戮'), + (0xF9D3, 'M', '陸'), + (0xF9D4, 'M', '倫'), + (0xF9D5, 'M', '崙'), + (0xF9D6, 'M', '淪'), + (0xF9D7, 'M', '輪'), + (0xF9D8, 'M', '律'), + (0xF9D9, 'M', '慄'), + (0xF9DA, 'M', '栗'), + (0xF9DB, 'M', '率'), + (0xF9DC, 'M', '隆'), + (0xF9DD, 'M', '利'), + (0xF9DE, 'M', '吏'), + (0xF9DF, 'M', '履'), + (0xF9E0, 'M', '易'), + (0xF9E1, 'M', '李'), + (0xF9E2, 'M', '梨'), + (0xF9E3, 'M', '泥'), + (0xF9E4, 'M', '理'), + (0xF9E5, 'M', '痢'), + (0xF9E6, 'M', '罹'), + (0xF9E7, 'M', '裏'), + (0xF9E8, 'M', '裡'), + (0xF9E9, 'M', '里'), + (0xF9EA, 'M', '離'), + (0xF9EB, 'M', '匿'), + (0xF9EC, 'M', '溺'), + (0xF9ED, 'M', '吝'), + (0xF9EE, 'M', '燐'), + (0xF9EF, 'M', '璘'), + (0xF9F0, 'M', '藺'), + (0xF9F1, 'M', '隣'), + (0xF9F2, 'M', '鱗'), + (0xF9F3, 'M', '麟'), + (0xF9F4, 'M', '林'), + (0xF9F5, 'M', '淋'), + (0xF9F6, 'M', '臨'), + (0xF9F7, 'M', '立'), + (0xF9F8, 'M', '笠'), + (0xF9F9, 'M', '粒'), + (0xF9FA, 'M', '狀'), + (0xF9FB, 'M', '炙'), + (0xF9FC, 'M', '識'), + (0xF9FD, 'M', '什'), + (0xF9FE, 'M', '茶'), + (0xF9FF, 'M', '刺'), + (0xFA00, 'M', '切'), + (0xFA01, 'M', '度'), + (0xFA02, 'M', '拓'), + (0xFA03, 'M', '糖'), + (0xFA04, 'M', '宅'), + (0xFA05, 'M', '洞'), + (0xFA06, 'M', '暴'), + (0xFA07, 'M', '輻'), + (0xFA08, 'M', '行'), + (0xFA09, 'M', '降'), + (0xFA0A, 'M', '見'), + (0xFA0B, 'M', '廓'), + (0xFA0C, 'M', '兀'), + (0xFA0D, 'M', '嗀'), + ] + +def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFA0E, 'V'), + (0xFA10, 'M', '塚'), + (0xFA11, 'V'), + (0xFA12, 'M', '晴'), + (0xFA13, 'V'), + (0xFA15, 'M', '凞'), + (0xFA16, 'M', '猪'), + (0xFA17, 'M', '益'), + (0xFA18, 'M', '礼'), + (0xFA19, 'M', '神'), + (0xFA1A, 'M', '祥'), + (0xFA1B, 'M', '福'), + (0xFA1C, 'M', '靖'), + (0xFA1D, 'M', '精'), + (0xFA1E, 'M', '羽'), + (0xFA1F, 'V'), + (0xFA20, 'M', '蘒'), + (0xFA21, 'V'), + (0xFA22, 'M', '諸'), + (0xFA23, 'V'), + (0xFA25, 'M', '逸'), + (0xFA26, 'M', '都'), + (0xFA27, 'V'), + (0xFA2A, 'M', '飯'), + (0xFA2B, 'M', '飼'), + (0xFA2C, 'M', '館'), + (0xFA2D, 'M', '鶴'), + (0xFA2E, 'M', '郞'), + (0xFA2F, 'M', '隷'), + (0xFA30, 'M', '侮'), + (0xFA31, 'M', '僧'), + (0xFA32, 'M', '免'), + (0xFA33, 'M', '勉'), + (0xFA34, 'M', '勤'), + (0xFA35, 'M', '卑'), + (0xFA36, 'M', '喝'), + (0xFA37, 'M', '嘆'), + (0xFA38, 'M', '器'), + (0xFA39, 'M', '塀'), + (0xFA3A, 'M', '墨'), + (0xFA3B, 'M', '層'), + (0xFA3C, 'M', '屮'), + (0xFA3D, 'M', '悔'), + (0xFA3E, 'M', '慨'), + (0xFA3F, 'M', '憎'), + (0xFA40, 'M', '懲'), + (0xFA41, 'M', '敏'), + (0xFA42, 'M', '既'), + (0xFA43, 'M', '暑'), + (0xFA44, 'M', '梅'), + (0xFA45, 'M', '海'), + (0xFA46, 'M', '渚'), + (0xFA47, 'M', '漢'), + (0xFA48, 'M', '煮'), + (0xFA49, 'M', '爫'), + (0xFA4A, 'M', '琢'), + (0xFA4B, 'M', '碑'), + (0xFA4C, 'M', '社'), + (0xFA4D, 'M', '祉'), + (0xFA4E, 'M', '祈'), + (0xFA4F, 'M', '祐'), + (0xFA50, 'M', '祖'), + (0xFA51, 'M', '祝'), + (0xFA52, 'M', '禍'), + (0xFA53, 'M', '禎'), + (0xFA54, 'M', '穀'), + (0xFA55, 'M', '突'), + (0xFA56, 'M', '節'), + (0xFA57, 'M', '練'), + (0xFA58, 'M', '縉'), + (0xFA59, 'M', '繁'), + (0xFA5A, 'M', '署'), + (0xFA5B, 'M', '者'), + (0xFA5C, 'M', '臭'), + (0xFA5D, 'M', '艹'), + (0xFA5F, 'M', '著'), + (0xFA60, 'M', '褐'), + (0xFA61, 'M', '視'), + (0xFA62, 'M', '謁'), + (0xFA63, 'M', '謹'), + (0xFA64, 'M', '賓'), + (0xFA65, 'M', '贈'), + (0xFA66, 'M', '辶'), + (0xFA67, 'M', '逸'), + (0xFA68, 'M', '難'), + (0xFA69, 'M', '響'), + (0xFA6A, 'M', '頻'), + (0xFA6B, 'M', '恵'), + (0xFA6C, 'M', '𤋮'), + (0xFA6D, 'M', '舘'), + (0xFA6E, 'X'), + (0xFA70, 'M', '並'), + (0xFA71, 'M', '况'), + (0xFA72, 'M', '全'), + (0xFA73, 'M', '侀'), + (0xFA74, 'M', '充'), + (0xFA75, 'M', '冀'), + (0xFA76, 'M', '勇'), + (0xFA77, 'M', '勺'), + (0xFA78, 'M', '喝'), + ] + +def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFA79, 'M', '啕'), + (0xFA7A, 'M', '喙'), + (0xFA7B, 'M', '嗢'), + (0xFA7C, 'M', '塚'), + (0xFA7D, 'M', '墳'), + (0xFA7E, 'M', '奄'), + (0xFA7F, 'M', '奔'), + (0xFA80, 'M', '婢'), + (0xFA81, 'M', '嬨'), + (0xFA82, 'M', '廒'), + (0xFA83, 'M', '廙'), + (0xFA84, 'M', '彩'), + (0xFA85, 'M', '徭'), + (0xFA86, 'M', '惘'), + (0xFA87, 'M', '慎'), + (0xFA88, 'M', '愈'), + (0xFA89, 'M', '憎'), + (0xFA8A, 'M', '慠'), + (0xFA8B, 'M', '懲'), + (0xFA8C, 'M', '戴'), + (0xFA8D, 'M', '揄'), + (0xFA8E, 'M', '搜'), + (0xFA8F, 'M', '摒'), + (0xFA90, 'M', '敖'), + (0xFA91, 'M', '晴'), + (0xFA92, 'M', '朗'), + (0xFA93, 'M', '望'), + (0xFA94, 'M', '杖'), + (0xFA95, 'M', '歹'), + (0xFA96, 'M', '殺'), + (0xFA97, 'M', '流'), + (0xFA98, 'M', '滛'), + (0xFA99, 'M', '滋'), + (0xFA9A, 'M', '漢'), + (0xFA9B, 'M', '瀞'), + (0xFA9C, 'M', '煮'), + (0xFA9D, 'M', '瞧'), + (0xFA9E, 'M', '爵'), + (0xFA9F, 'M', '犯'), + (0xFAA0, 'M', '猪'), + (0xFAA1, 'M', '瑱'), + (0xFAA2, 'M', '甆'), + (0xFAA3, 'M', '画'), + (0xFAA4, 'M', '瘝'), + (0xFAA5, 'M', '瘟'), + (0xFAA6, 'M', '益'), + (0xFAA7, 'M', '盛'), + (0xFAA8, 'M', '直'), + (0xFAA9, 'M', '睊'), + (0xFAAA, 'M', '着'), + (0xFAAB, 'M', '磌'), + (0xFAAC, 'M', '窱'), + (0xFAAD, 'M', '節'), + (0xFAAE, 'M', '类'), + (0xFAAF, 'M', '絛'), + (0xFAB0, 'M', '練'), + (0xFAB1, 'M', '缾'), + (0xFAB2, 'M', '者'), + (0xFAB3, 'M', '荒'), + (0xFAB4, 'M', '華'), + (0xFAB5, 'M', '蝹'), + (0xFAB6, 'M', '襁'), + (0xFAB7, 'M', '覆'), + (0xFAB8, 'M', '視'), + (0xFAB9, 'M', '調'), + (0xFABA, 'M', '諸'), + (0xFABB, 'M', '請'), + (0xFABC, 'M', '謁'), + (0xFABD, 'M', '諾'), + (0xFABE, 'M', '諭'), + (0xFABF, 'M', '謹'), + (0xFAC0, 'M', '變'), + (0xFAC1, 'M', '贈'), + (0xFAC2, 'M', '輸'), + (0xFAC3, 'M', '遲'), + (0xFAC4, 'M', '醙'), + (0xFAC5, 'M', '鉶'), + (0xFAC6, 'M', '陼'), + (0xFAC7, 'M', '難'), + (0xFAC8, 'M', '靖'), + (0xFAC9, 'M', '韛'), + (0xFACA, 'M', '響'), + (0xFACB, 'M', '頋'), + (0xFACC, 'M', '頻'), + (0xFACD, 'M', '鬒'), + (0xFACE, 'M', '龜'), + (0xFACF, 'M', '𢡊'), + (0xFAD0, 'M', '𢡄'), + (0xFAD1, 'M', '𣏕'), + (0xFAD2, 'M', '㮝'), + (0xFAD3, 'M', '䀘'), + (0xFAD4, 'M', '䀹'), + (0xFAD5, 'M', '𥉉'), + (0xFAD6, 'M', '𥳐'), + (0xFAD7, 'M', '𧻓'), + (0xFAD8, 'M', '齃'), + (0xFAD9, 'M', '龎'), + (0xFADA, 'X'), + (0xFB00, 'M', 'ff'), + (0xFB01, 'M', 'fi'), + ] + +def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFB02, 'M', 'fl'), + (0xFB03, 'M', 'ffi'), + (0xFB04, 'M', 'ffl'), + (0xFB05, 'M', 'st'), + (0xFB07, 'X'), + (0xFB13, 'M', 'մն'), + (0xFB14, 'M', 'մե'), + (0xFB15, 'M', 'մի'), + (0xFB16, 'M', 'վն'), + (0xFB17, 'M', 'մխ'), + (0xFB18, 'X'), + (0xFB1D, 'M', 'יִ'), + (0xFB1E, 'V'), + (0xFB1F, 'M', 'ײַ'), + (0xFB20, 'M', 'ע'), + (0xFB21, 'M', 'א'), + (0xFB22, 'M', 'ד'), + (0xFB23, 'M', 'ה'), + (0xFB24, 'M', 'כ'), + (0xFB25, 'M', 'ל'), + (0xFB26, 'M', 'ם'), + (0xFB27, 'M', 'ר'), + (0xFB28, 'M', 'ת'), + (0xFB29, '3', '+'), + (0xFB2A, 'M', 'שׁ'), + (0xFB2B, 'M', 'שׂ'), + (0xFB2C, 'M', 'שּׁ'), + (0xFB2D, 'M', 'שּׂ'), + (0xFB2E, 'M', 'אַ'), + (0xFB2F, 'M', 'אָ'), + (0xFB30, 'M', 'אּ'), + (0xFB31, 'M', 'בּ'), + (0xFB32, 'M', 'גּ'), + (0xFB33, 'M', 'דּ'), + (0xFB34, 'M', 'הּ'), + (0xFB35, 'M', 'וּ'), + (0xFB36, 'M', 'זּ'), + (0xFB37, 'X'), + (0xFB38, 'M', 'טּ'), + (0xFB39, 'M', 'יּ'), + (0xFB3A, 'M', 'ךּ'), + (0xFB3B, 'M', 'כּ'), + (0xFB3C, 'M', 'לּ'), + (0xFB3D, 'X'), + (0xFB3E, 'M', 'מּ'), + (0xFB3F, 'X'), + (0xFB40, 'M', 'נּ'), + (0xFB41, 'M', 'סּ'), + (0xFB42, 'X'), + (0xFB43, 'M', 'ףּ'), + (0xFB44, 'M', 'פּ'), + (0xFB45, 'X'), + (0xFB46, 'M', 'צּ'), + (0xFB47, 'M', 'קּ'), + (0xFB48, 'M', 'רּ'), + (0xFB49, 'M', 'שּ'), + (0xFB4A, 'M', 'תּ'), + (0xFB4B, 'M', 'וֹ'), + (0xFB4C, 'M', 'בֿ'), + (0xFB4D, 'M', 'כֿ'), + (0xFB4E, 'M', 'פֿ'), + (0xFB4F, 'M', 'אל'), + (0xFB50, 'M', 'ٱ'), + (0xFB52, 'M', 'ٻ'), + (0xFB56, 'M', 'پ'), + (0xFB5A, 'M', 'ڀ'), + (0xFB5E, 'M', 'ٺ'), + (0xFB62, 'M', 'ٿ'), + (0xFB66, 'M', 'ٹ'), + (0xFB6A, 'M', 'ڤ'), + (0xFB6E, 'M', 'ڦ'), + (0xFB72, 'M', 'ڄ'), + (0xFB76, 'M', 'ڃ'), + (0xFB7A, 'M', 'چ'), + (0xFB7E, 'M', 'ڇ'), + (0xFB82, 'M', 'ڍ'), + (0xFB84, 'M', 'ڌ'), + (0xFB86, 'M', 'ڎ'), + (0xFB88, 'M', 'ڈ'), + (0xFB8A, 'M', 'ژ'), + (0xFB8C, 'M', 'ڑ'), + (0xFB8E, 'M', 'ک'), + (0xFB92, 'M', 'گ'), + (0xFB96, 'M', 'ڳ'), + (0xFB9A, 'M', 'ڱ'), + (0xFB9E, 'M', 'ں'), + (0xFBA0, 'M', 'ڻ'), + (0xFBA4, 'M', 'ۀ'), + (0xFBA6, 'M', 'ہ'), + (0xFBAA, 'M', 'ھ'), + (0xFBAE, 'M', 'ے'), + (0xFBB0, 'M', 'ۓ'), + (0xFBB2, 'V'), + (0xFBC3, 'X'), + (0xFBD3, 'M', 'ڭ'), + (0xFBD7, 'M', 'ۇ'), + (0xFBD9, 'M', 'ۆ'), + (0xFBDB, 'M', 'ۈ'), + (0xFBDD, 'M', 'ۇٴ'), + (0xFBDE, 'M', 'ۋ'), + ] + +def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFBE0, 'M', 'ۅ'), + (0xFBE2, 'M', 'ۉ'), + (0xFBE4, 'M', 'ې'), + (0xFBE8, 'M', 'ى'), + (0xFBEA, 'M', 'ئا'), + (0xFBEC, 'M', 'ئە'), + (0xFBEE, 'M', 'ئو'), + (0xFBF0, 'M', 'ئۇ'), + (0xFBF2, 'M', 'ئۆ'), + (0xFBF4, 'M', 'ئۈ'), + (0xFBF6, 'M', 'ئې'), + (0xFBF9, 'M', 'ئى'), + (0xFBFC, 'M', 'ی'), + (0xFC00, 'M', 'ئج'), + (0xFC01, 'M', 'ئح'), + (0xFC02, 'M', 'ئم'), + (0xFC03, 'M', 'ئى'), + (0xFC04, 'M', 'ئي'), + (0xFC05, 'M', 'بج'), + (0xFC06, 'M', 'بح'), + (0xFC07, 'M', 'بخ'), + (0xFC08, 'M', 'بم'), + (0xFC09, 'M', 'بى'), + (0xFC0A, 'M', 'بي'), + (0xFC0B, 'M', 'تج'), + (0xFC0C, 'M', 'تح'), + (0xFC0D, 'M', 'تخ'), + (0xFC0E, 'M', 'تم'), + (0xFC0F, 'M', 'تى'), + (0xFC10, 'M', 'تي'), + (0xFC11, 'M', 'ثج'), + (0xFC12, 'M', 'ثم'), + (0xFC13, 'M', 'ثى'), + (0xFC14, 'M', 'ثي'), + (0xFC15, 'M', 'جح'), + (0xFC16, 'M', 'جم'), + (0xFC17, 'M', 'حج'), + (0xFC18, 'M', 'حم'), + (0xFC19, 'M', 'خج'), + (0xFC1A, 'M', 'خح'), + (0xFC1B, 'M', 'خم'), + (0xFC1C, 'M', 'سج'), + (0xFC1D, 'M', 'سح'), + (0xFC1E, 'M', 'سخ'), + (0xFC1F, 'M', 'سم'), + (0xFC20, 'M', 'صح'), + (0xFC21, 'M', 'صم'), + (0xFC22, 'M', 'ضج'), + (0xFC23, 'M', 'ضح'), + (0xFC24, 'M', 'ضخ'), + (0xFC25, 'M', 'ضم'), + (0xFC26, 'M', 'طح'), + (0xFC27, 'M', 'طم'), + (0xFC28, 'M', 'ظم'), + (0xFC29, 'M', 'عج'), + (0xFC2A, 'M', 'عم'), + (0xFC2B, 'M', 'غج'), + (0xFC2C, 'M', 'غم'), + (0xFC2D, 'M', 'فج'), + (0xFC2E, 'M', 'فح'), + (0xFC2F, 'M', 'فخ'), + (0xFC30, 'M', 'فم'), + (0xFC31, 'M', 'فى'), + (0xFC32, 'M', 'في'), + (0xFC33, 'M', 'قح'), + (0xFC34, 'M', 'قم'), + (0xFC35, 'M', 'قى'), + (0xFC36, 'M', 'قي'), + (0xFC37, 'M', 'كا'), + (0xFC38, 'M', 'كج'), + (0xFC39, 'M', 'كح'), + (0xFC3A, 'M', 'كخ'), + (0xFC3B, 'M', 'كل'), + (0xFC3C, 'M', 'كم'), + (0xFC3D, 'M', 'كى'), + (0xFC3E, 'M', 'كي'), + (0xFC3F, 'M', 'لج'), + (0xFC40, 'M', 'لح'), + (0xFC41, 'M', 'لخ'), + (0xFC42, 'M', 'لم'), + (0xFC43, 'M', 'لى'), + (0xFC44, 'M', 'لي'), + (0xFC45, 'M', 'مج'), + (0xFC46, 'M', 'مح'), + (0xFC47, 'M', 'مخ'), + (0xFC48, 'M', 'مم'), + (0xFC49, 'M', 'مى'), + (0xFC4A, 'M', 'مي'), + (0xFC4B, 'M', 'نج'), + (0xFC4C, 'M', 'نح'), + (0xFC4D, 'M', 'نخ'), + (0xFC4E, 'M', 'نم'), + (0xFC4F, 'M', 'نى'), + (0xFC50, 'M', 'ني'), + (0xFC51, 'M', 'هج'), + (0xFC52, 'M', 'هم'), + (0xFC53, 'M', 'هى'), + (0xFC54, 'M', 'هي'), + (0xFC55, 'M', 'يج'), + (0xFC56, 'M', 'يح'), + ] + +def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFC57, 'M', 'يخ'), + (0xFC58, 'M', 'يم'), + (0xFC59, 'M', 'يى'), + (0xFC5A, 'M', 'يي'), + (0xFC5B, 'M', 'ذٰ'), + (0xFC5C, 'M', 'رٰ'), + (0xFC5D, 'M', 'ىٰ'), + (0xFC5E, '3', ' ٌّ'), + (0xFC5F, '3', ' ٍّ'), + (0xFC60, '3', ' َّ'), + (0xFC61, '3', ' ُّ'), + (0xFC62, '3', ' ِّ'), + (0xFC63, '3', ' ّٰ'), + (0xFC64, 'M', 'ئر'), + (0xFC65, 'M', 'ئز'), + (0xFC66, 'M', 'ئم'), + (0xFC67, 'M', 'ئن'), + (0xFC68, 'M', 'ئى'), + (0xFC69, 'M', 'ئي'), + (0xFC6A, 'M', 'بر'), + (0xFC6B, 'M', 'بز'), + (0xFC6C, 'M', 'بم'), + (0xFC6D, 'M', 'بن'), + (0xFC6E, 'M', 'بى'), + (0xFC6F, 'M', 'بي'), + (0xFC70, 'M', 'تر'), + (0xFC71, 'M', 'تز'), + (0xFC72, 'M', 'تم'), + (0xFC73, 'M', 'تن'), + (0xFC74, 'M', 'تى'), + (0xFC75, 'M', 'تي'), + (0xFC76, 'M', 'ثر'), + (0xFC77, 'M', 'ثز'), + (0xFC78, 'M', 'ثم'), + (0xFC79, 'M', 'ثن'), + (0xFC7A, 'M', 'ثى'), + (0xFC7B, 'M', 'ثي'), + (0xFC7C, 'M', 'فى'), + (0xFC7D, 'M', 'في'), + (0xFC7E, 'M', 'قى'), + (0xFC7F, 'M', 'قي'), + (0xFC80, 'M', 'كا'), + (0xFC81, 'M', 'كل'), + (0xFC82, 'M', 'كم'), + (0xFC83, 'M', 'كى'), + (0xFC84, 'M', 'كي'), + (0xFC85, 'M', 'لم'), + (0xFC86, 'M', 'لى'), + (0xFC87, 'M', 'لي'), + (0xFC88, 'M', 'ما'), + (0xFC89, 'M', 'مم'), + (0xFC8A, 'M', 'نر'), + (0xFC8B, 'M', 'نز'), + (0xFC8C, 'M', 'نم'), + (0xFC8D, 'M', 'نن'), + (0xFC8E, 'M', 'نى'), + (0xFC8F, 'M', 'ني'), + (0xFC90, 'M', 'ىٰ'), + (0xFC91, 'M', 'ير'), + (0xFC92, 'M', 'يز'), + (0xFC93, 'M', 'يم'), + (0xFC94, 'M', 'ين'), + (0xFC95, 'M', 'يى'), + (0xFC96, 'M', 'يي'), + (0xFC97, 'M', 'ئج'), + (0xFC98, 'M', 'ئح'), + (0xFC99, 'M', 'ئخ'), + (0xFC9A, 'M', 'ئم'), + (0xFC9B, 'M', 'ئه'), + (0xFC9C, 'M', 'بج'), + (0xFC9D, 'M', 'بح'), + (0xFC9E, 'M', 'بخ'), + (0xFC9F, 'M', 'بم'), + (0xFCA0, 'M', 'به'), + (0xFCA1, 'M', 'تج'), + (0xFCA2, 'M', 'تح'), + (0xFCA3, 'M', 'تخ'), + (0xFCA4, 'M', 'تم'), + (0xFCA5, 'M', 'ته'), + (0xFCA6, 'M', 'ثم'), + (0xFCA7, 'M', 'جح'), + (0xFCA8, 'M', 'جم'), + (0xFCA9, 'M', 'حج'), + (0xFCAA, 'M', 'حم'), + (0xFCAB, 'M', 'خج'), + (0xFCAC, 'M', 'خم'), + (0xFCAD, 'M', 'سج'), + (0xFCAE, 'M', 'سح'), + (0xFCAF, 'M', 'سخ'), + (0xFCB0, 'M', 'سم'), + (0xFCB1, 'M', 'صح'), + (0xFCB2, 'M', 'صخ'), + (0xFCB3, 'M', 'صم'), + (0xFCB4, 'M', 'ضج'), + (0xFCB5, 'M', 'ضح'), + (0xFCB6, 'M', 'ضخ'), + (0xFCB7, 'M', 'ضم'), + (0xFCB8, 'M', 'طح'), + (0xFCB9, 'M', 'ظم'), + (0xFCBA, 'M', 'عج'), + ] + +def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFCBB, 'M', 'عم'), + (0xFCBC, 'M', 'غج'), + (0xFCBD, 'M', 'غم'), + (0xFCBE, 'M', 'فج'), + (0xFCBF, 'M', 'فح'), + (0xFCC0, 'M', 'فخ'), + (0xFCC1, 'M', 'فم'), + (0xFCC2, 'M', 'قح'), + (0xFCC3, 'M', 'قم'), + (0xFCC4, 'M', 'كج'), + (0xFCC5, 'M', 'كح'), + (0xFCC6, 'M', 'كخ'), + (0xFCC7, 'M', 'كل'), + (0xFCC8, 'M', 'كم'), + (0xFCC9, 'M', 'لج'), + (0xFCCA, 'M', 'لح'), + (0xFCCB, 'M', 'لخ'), + (0xFCCC, 'M', 'لم'), + (0xFCCD, 'M', 'له'), + (0xFCCE, 'M', 'مج'), + (0xFCCF, 'M', 'مح'), + (0xFCD0, 'M', 'مخ'), + (0xFCD1, 'M', 'مم'), + (0xFCD2, 'M', 'نج'), + (0xFCD3, 'M', 'نح'), + (0xFCD4, 'M', 'نخ'), + (0xFCD5, 'M', 'نم'), + (0xFCD6, 'M', 'نه'), + (0xFCD7, 'M', 'هج'), + (0xFCD8, 'M', 'هم'), + (0xFCD9, 'M', 'هٰ'), + (0xFCDA, 'M', 'يج'), + (0xFCDB, 'M', 'يح'), + (0xFCDC, 'M', 'يخ'), + (0xFCDD, 'M', 'يم'), + (0xFCDE, 'M', 'يه'), + (0xFCDF, 'M', 'ئم'), + (0xFCE0, 'M', 'ئه'), + (0xFCE1, 'M', 'بم'), + (0xFCE2, 'M', 'به'), + (0xFCE3, 'M', 'تم'), + (0xFCE4, 'M', 'ته'), + (0xFCE5, 'M', 'ثم'), + (0xFCE6, 'M', 'ثه'), + (0xFCE7, 'M', 'سم'), + (0xFCE8, 'M', 'سه'), + (0xFCE9, 'M', 'شم'), + (0xFCEA, 'M', 'شه'), + (0xFCEB, 'M', 'كل'), + (0xFCEC, 'M', 'كم'), + (0xFCED, 'M', 'لم'), + (0xFCEE, 'M', 'نم'), + (0xFCEF, 'M', 'نه'), + (0xFCF0, 'M', 'يم'), + (0xFCF1, 'M', 'يه'), + (0xFCF2, 'M', 'ـَّ'), + (0xFCF3, 'M', 'ـُّ'), + (0xFCF4, 'M', 'ـِّ'), + (0xFCF5, 'M', 'طى'), + (0xFCF6, 'M', 'طي'), + (0xFCF7, 'M', 'عى'), + (0xFCF8, 'M', 'عي'), + (0xFCF9, 'M', 'غى'), + (0xFCFA, 'M', 'غي'), + (0xFCFB, 'M', 'سى'), + (0xFCFC, 'M', 'سي'), + (0xFCFD, 'M', 'شى'), + (0xFCFE, 'M', 'شي'), + (0xFCFF, 'M', 'حى'), + (0xFD00, 'M', 'حي'), + (0xFD01, 'M', 'جى'), + (0xFD02, 'M', 'جي'), + (0xFD03, 'M', 'خى'), + (0xFD04, 'M', 'خي'), + (0xFD05, 'M', 'صى'), + (0xFD06, 'M', 'صي'), + (0xFD07, 'M', 'ضى'), + (0xFD08, 'M', 'ضي'), + (0xFD09, 'M', 'شج'), + (0xFD0A, 'M', 'شح'), + (0xFD0B, 'M', 'شخ'), + (0xFD0C, 'M', 'شم'), + (0xFD0D, 'M', 'شر'), + (0xFD0E, 'M', 'سر'), + (0xFD0F, 'M', 'صر'), + (0xFD10, 'M', 'ضر'), + (0xFD11, 'M', 'طى'), + (0xFD12, 'M', 'طي'), + (0xFD13, 'M', 'عى'), + (0xFD14, 'M', 'عي'), + (0xFD15, 'M', 'غى'), + (0xFD16, 'M', 'غي'), + (0xFD17, 'M', 'سى'), + (0xFD18, 'M', 'سي'), + (0xFD19, 'M', 'شى'), + (0xFD1A, 'M', 'شي'), + (0xFD1B, 'M', 'حى'), + (0xFD1C, 'M', 'حي'), + (0xFD1D, 'M', 'جى'), + (0xFD1E, 'M', 'جي'), + ] + +def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFD1F, 'M', 'خى'), + (0xFD20, 'M', 'خي'), + (0xFD21, 'M', 'صى'), + (0xFD22, 'M', 'صي'), + (0xFD23, 'M', 'ضى'), + (0xFD24, 'M', 'ضي'), + (0xFD25, 'M', 'شج'), + (0xFD26, 'M', 'شح'), + (0xFD27, 'M', 'شخ'), + (0xFD28, 'M', 'شم'), + (0xFD29, 'M', 'شر'), + (0xFD2A, 'M', 'سر'), + (0xFD2B, 'M', 'صر'), + (0xFD2C, 'M', 'ضر'), + (0xFD2D, 'M', 'شج'), + (0xFD2E, 'M', 'شح'), + (0xFD2F, 'M', 'شخ'), + (0xFD30, 'M', 'شم'), + (0xFD31, 'M', 'سه'), + (0xFD32, 'M', 'شه'), + (0xFD33, 'M', 'طم'), + (0xFD34, 'M', 'سج'), + (0xFD35, 'M', 'سح'), + (0xFD36, 'M', 'سخ'), + (0xFD37, 'M', 'شج'), + (0xFD38, 'M', 'شح'), + (0xFD39, 'M', 'شخ'), + (0xFD3A, 'M', 'طم'), + (0xFD3B, 'M', 'ظم'), + (0xFD3C, 'M', 'اً'), + (0xFD3E, 'V'), + (0xFD50, 'M', 'تجم'), + (0xFD51, 'M', 'تحج'), + (0xFD53, 'M', 'تحم'), + (0xFD54, 'M', 'تخم'), + (0xFD55, 'M', 'تمج'), + (0xFD56, 'M', 'تمح'), + (0xFD57, 'M', 'تمخ'), + (0xFD58, 'M', 'جمح'), + (0xFD5A, 'M', 'حمي'), + (0xFD5B, 'M', 'حمى'), + (0xFD5C, 'M', 'سحج'), + (0xFD5D, 'M', 'سجح'), + (0xFD5E, 'M', 'سجى'), + (0xFD5F, 'M', 'سمح'), + (0xFD61, 'M', 'سمج'), + (0xFD62, 'M', 'سمم'), + (0xFD64, 'M', 'صحح'), + (0xFD66, 'M', 'صمم'), + (0xFD67, 'M', 'شحم'), + (0xFD69, 'M', 'شجي'), + (0xFD6A, 'M', 'شمخ'), + (0xFD6C, 'M', 'شمم'), + (0xFD6E, 'M', 'ضحى'), + (0xFD6F, 'M', 'ضخم'), + (0xFD71, 'M', 'طمح'), + (0xFD73, 'M', 'طمم'), + (0xFD74, 'M', 'طمي'), + (0xFD75, 'M', 'عجم'), + (0xFD76, 'M', 'عمم'), + (0xFD78, 'M', 'عمى'), + (0xFD79, 'M', 'غمم'), + (0xFD7A, 'M', 'غمي'), + (0xFD7B, 'M', 'غمى'), + (0xFD7C, 'M', 'فخم'), + (0xFD7E, 'M', 'قمح'), + (0xFD7F, 'M', 'قمم'), + (0xFD80, 'M', 'لحم'), + (0xFD81, 'M', 'لحي'), + (0xFD82, 'M', 'لحى'), + (0xFD83, 'M', 'لجج'), + (0xFD85, 'M', 'لخم'), + (0xFD87, 'M', 'لمح'), + (0xFD89, 'M', 'محج'), + (0xFD8A, 'M', 'محم'), + (0xFD8B, 'M', 'محي'), + (0xFD8C, 'M', 'مجح'), + (0xFD8D, 'M', 'مجم'), + (0xFD8E, 'M', 'مخج'), + (0xFD8F, 'M', 'مخم'), + (0xFD90, 'X'), + (0xFD92, 'M', 'مجخ'), + (0xFD93, 'M', 'همج'), + (0xFD94, 'M', 'همم'), + (0xFD95, 'M', 'نحم'), + (0xFD96, 'M', 'نحى'), + (0xFD97, 'M', 'نجم'), + (0xFD99, 'M', 'نجى'), + (0xFD9A, 'M', 'نمي'), + (0xFD9B, 'M', 'نمى'), + (0xFD9C, 'M', 'يمم'), + (0xFD9E, 'M', 'بخي'), + (0xFD9F, 'M', 'تجي'), + (0xFDA0, 'M', 'تجى'), + (0xFDA1, 'M', 'تخي'), + (0xFDA2, 'M', 'تخى'), + (0xFDA3, 'M', 'تمي'), + (0xFDA4, 'M', 'تمى'), + (0xFDA5, 'M', 'جمي'), + (0xFDA6, 'M', 'جحى'), + ] + +def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFDA7, 'M', 'جمى'), + (0xFDA8, 'M', 'سخى'), + (0xFDA9, 'M', 'صحي'), + (0xFDAA, 'M', 'شحي'), + (0xFDAB, 'M', 'ضحي'), + (0xFDAC, 'M', 'لجي'), + (0xFDAD, 'M', 'لمي'), + (0xFDAE, 'M', 'يحي'), + (0xFDAF, 'M', 'يجي'), + (0xFDB0, 'M', 'يمي'), + (0xFDB1, 'M', 'ممي'), + (0xFDB2, 'M', 'قمي'), + (0xFDB3, 'M', 'نحي'), + (0xFDB4, 'M', 'قمح'), + (0xFDB5, 'M', 'لحم'), + (0xFDB6, 'M', 'عمي'), + (0xFDB7, 'M', 'كمي'), + (0xFDB8, 'M', 'نجح'), + (0xFDB9, 'M', 'مخي'), + (0xFDBA, 'M', 'لجم'), + (0xFDBB, 'M', 'كمم'), + (0xFDBC, 'M', 'لجم'), + (0xFDBD, 'M', 'نجح'), + (0xFDBE, 'M', 'جحي'), + (0xFDBF, 'M', 'حجي'), + (0xFDC0, 'M', 'مجي'), + (0xFDC1, 'M', 'فمي'), + (0xFDC2, 'M', 'بحي'), + (0xFDC3, 'M', 'كمم'), + (0xFDC4, 'M', 'عجم'), + (0xFDC5, 'M', 'صمم'), + (0xFDC6, 'M', 'سخي'), + (0xFDC7, 'M', 'نجي'), + (0xFDC8, 'X'), + (0xFDCF, 'V'), + (0xFDD0, 'X'), + (0xFDF0, 'M', 'صلے'), + (0xFDF1, 'M', 'قلے'), + (0xFDF2, 'M', 'الله'), + (0xFDF3, 'M', 'اكبر'), + (0xFDF4, 'M', 'محمد'), + (0xFDF5, 'M', 'صلعم'), + (0xFDF6, 'M', 'رسول'), + (0xFDF7, 'M', 'عليه'), + (0xFDF8, 'M', 'وسلم'), + (0xFDF9, 'M', 'صلى'), + (0xFDFA, '3', 'صلى الله عليه وسلم'), + (0xFDFB, '3', 'جل جلاله'), + (0xFDFC, 'M', 'ریال'), + (0xFDFD, 'V'), + (0xFE00, 'I'), + (0xFE10, '3', ','), + (0xFE11, 'M', '、'), + (0xFE12, 'X'), + (0xFE13, '3', ':'), + (0xFE14, '3', ';'), + (0xFE15, '3', '!'), + (0xFE16, '3', '?'), + (0xFE17, 'M', '〖'), + (0xFE18, 'M', '〗'), + (0xFE19, 'X'), + (0xFE20, 'V'), + (0xFE30, 'X'), + (0xFE31, 'M', '—'), + (0xFE32, 'M', '–'), + (0xFE33, '3', '_'), + (0xFE35, '3', '('), + (0xFE36, '3', ')'), + (0xFE37, '3', '{'), + (0xFE38, '3', '}'), + (0xFE39, 'M', '〔'), + (0xFE3A, 'M', '〕'), + (0xFE3B, 'M', '【'), + (0xFE3C, 'M', '】'), + (0xFE3D, 'M', '《'), + (0xFE3E, 'M', '》'), + (0xFE3F, 'M', '〈'), + (0xFE40, 'M', '〉'), + (0xFE41, 'M', '「'), + (0xFE42, 'M', '」'), + (0xFE43, 'M', '『'), + (0xFE44, 'M', '』'), + (0xFE45, 'V'), + (0xFE47, '3', '['), + (0xFE48, '3', ']'), + (0xFE49, '3', ' ̅'), + (0xFE4D, '3', '_'), + (0xFE50, '3', ','), + (0xFE51, 'M', '、'), + (0xFE52, 'X'), + (0xFE54, '3', ';'), + (0xFE55, '3', ':'), + (0xFE56, '3', '?'), + (0xFE57, '3', '!'), + (0xFE58, 'M', '—'), + (0xFE59, '3', '('), + (0xFE5A, '3', ')'), + (0xFE5B, '3', '{'), + (0xFE5C, '3', '}'), + (0xFE5D, 'M', '〔'), + ] + +def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFE5E, 'M', '〕'), + (0xFE5F, '3', '#'), + (0xFE60, '3', '&'), + (0xFE61, '3', '*'), + (0xFE62, '3', '+'), + (0xFE63, 'M', '-'), + (0xFE64, '3', '<'), + (0xFE65, '3', '>'), + (0xFE66, '3', '='), + (0xFE67, 'X'), + (0xFE68, '3', '\\'), + (0xFE69, '3', '$'), + (0xFE6A, '3', '%'), + (0xFE6B, '3', '@'), + (0xFE6C, 'X'), + (0xFE70, '3', ' ً'), + (0xFE71, 'M', 'ـً'), + (0xFE72, '3', ' ٌ'), + (0xFE73, 'V'), + (0xFE74, '3', ' ٍ'), + (0xFE75, 'X'), + (0xFE76, '3', ' َ'), + (0xFE77, 'M', 'ـَ'), + (0xFE78, '3', ' ُ'), + (0xFE79, 'M', 'ـُ'), + (0xFE7A, '3', ' ِ'), + (0xFE7B, 'M', 'ـِ'), + (0xFE7C, '3', ' ّ'), + (0xFE7D, 'M', 'ـّ'), + (0xFE7E, '3', ' ْ'), + (0xFE7F, 'M', 'ـْ'), + (0xFE80, 'M', 'ء'), + (0xFE81, 'M', 'آ'), + (0xFE83, 'M', 'أ'), + (0xFE85, 'M', 'ؤ'), + (0xFE87, 'M', 'إ'), + (0xFE89, 'M', 'ئ'), + (0xFE8D, 'M', 'ا'), + (0xFE8F, 'M', 'ب'), + (0xFE93, 'M', 'ة'), + (0xFE95, 'M', 'ت'), + (0xFE99, 'M', 'ث'), + (0xFE9D, 'M', 'ج'), + (0xFEA1, 'M', 'ح'), + (0xFEA5, 'M', 'خ'), + (0xFEA9, 'M', 'د'), + (0xFEAB, 'M', 'ذ'), + (0xFEAD, 'M', 'ر'), + (0xFEAF, 'M', 'ز'), + (0xFEB1, 'M', 'س'), + (0xFEB5, 'M', 'ش'), + (0xFEB9, 'M', 'ص'), + (0xFEBD, 'M', 'ض'), + (0xFEC1, 'M', 'ط'), + (0xFEC5, 'M', 'ظ'), + (0xFEC9, 'M', 'ع'), + (0xFECD, 'M', 'غ'), + (0xFED1, 'M', 'ف'), + (0xFED5, 'M', 'ق'), + (0xFED9, 'M', 'ك'), + (0xFEDD, 'M', 'ل'), + (0xFEE1, 'M', 'م'), + (0xFEE5, 'M', 'ن'), + (0xFEE9, 'M', 'ه'), + (0xFEED, 'M', 'و'), + (0xFEEF, 'M', 'ى'), + (0xFEF1, 'M', 'ي'), + (0xFEF5, 'M', 'لآ'), + (0xFEF7, 'M', 'لأ'), + (0xFEF9, 'M', 'لإ'), + (0xFEFB, 'M', 'لا'), + (0xFEFD, 'X'), + (0xFEFF, 'I'), + (0xFF00, 'X'), + (0xFF01, '3', '!'), + (0xFF02, '3', '"'), + (0xFF03, '3', '#'), + (0xFF04, '3', '$'), + (0xFF05, '3', '%'), + (0xFF06, '3', '&'), + (0xFF07, '3', '\''), + (0xFF08, '3', '('), + (0xFF09, '3', ')'), + (0xFF0A, '3', '*'), + (0xFF0B, '3', '+'), + (0xFF0C, '3', ','), + (0xFF0D, 'M', '-'), + (0xFF0E, 'M', '.'), + (0xFF0F, '3', '/'), + (0xFF10, 'M', '0'), + (0xFF11, 'M', '1'), + (0xFF12, 'M', '2'), + (0xFF13, 'M', '3'), + (0xFF14, 'M', '4'), + (0xFF15, 'M', '5'), + (0xFF16, 'M', '6'), + (0xFF17, 'M', '7'), + (0xFF18, 'M', '8'), + (0xFF19, 'M', '9'), + (0xFF1A, '3', ':'), + ] + +def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFF1B, '3', ';'), + (0xFF1C, '3', '<'), + (0xFF1D, '3', '='), + (0xFF1E, '3', '>'), + (0xFF1F, '3', '?'), + (0xFF20, '3', '@'), + (0xFF21, 'M', 'a'), + (0xFF22, 'M', 'b'), + (0xFF23, 'M', 'c'), + (0xFF24, 'M', 'd'), + (0xFF25, 'M', 'e'), + (0xFF26, 'M', 'f'), + (0xFF27, 'M', 'g'), + (0xFF28, 'M', 'h'), + (0xFF29, 'M', 'i'), + (0xFF2A, 'M', 'j'), + (0xFF2B, 'M', 'k'), + (0xFF2C, 'M', 'l'), + (0xFF2D, 'M', 'm'), + (0xFF2E, 'M', 'n'), + (0xFF2F, 'M', 'o'), + (0xFF30, 'M', 'p'), + (0xFF31, 'M', 'q'), + (0xFF32, 'M', 'r'), + (0xFF33, 'M', 's'), + (0xFF34, 'M', 't'), + (0xFF35, 'M', 'u'), + (0xFF36, 'M', 'v'), + (0xFF37, 'M', 'w'), + (0xFF38, 'M', 'x'), + (0xFF39, 'M', 'y'), + (0xFF3A, 'M', 'z'), + (0xFF3B, '3', '['), + (0xFF3C, '3', '\\'), + (0xFF3D, '3', ']'), + (0xFF3E, '3', '^'), + (0xFF3F, '3', '_'), + (0xFF40, '3', '`'), + (0xFF41, 'M', 'a'), + (0xFF42, 'M', 'b'), + (0xFF43, 'M', 'c'), + (0xFF44, 'M', 'd'), + (0xFF45, 'M', 'e'), + (0xFF46, 'M', 'f'), + (0xFF47, 'M', 'g'), + (0xFF48, 'M', 'h'), + (0xFF49, 'M', 'i'), + (0xFF4A, 'M', 'j'), + (0xFF4B, 'M', 'k'), + (0xFF4C, 'M', 'l'), + (0xFF4D, 'M', 'm'), + (0xFF4E, 'M', 'n'), + (0xFF4F, 'M', 'o'), + (0xFF50, 'M', 'p'), + (0xFF51, 'M', 'q'), + (0xFF52, 'M', 'r'), + (0xFF53, 'M', 's'), + (0xFF54, 'M', 't'), + (0xFF55, 'M', 'u'), + (0xFF56, 'M', 'v'), + (0xFF57, 'M', 'w'), + (0xFF58, 'M', 'x'), + (0xFF59, 'M', 'y'), + (0xFF5A, 'M', 'z'), + (0xFF5B, '3', '{'), + (0xFF5C, '3', '|'), + (0xFF5D, '3', '}'), + (0xFF5E, '3', '~'), + (0xFF5F, 'M', '⦅'), + (0xFF60, 'M', '⦆'), + (0xFF61, 'M', '.'), + (0xFF62, 'M', '「'), + (0xFF63, 'M', '」'), + (0xFF64, 'M', '、'), + (0xFF65, 'M', '・'), + (0xFF66, 'M', 'ヲ'), + (0xFF67, 'M', 'ァ'), + (0xFF68, 'M', 'ィ'), + (0xFF69, 'M', 'ゥ'), + (0xFF6A, 'M', 'ェ'), + (0xFF6B, 'M', 'ォ'), + (0xFF6C, 'M', 'ャ'), + (0xFF6D, 'M', 'ュ'), + (0xFF6E, 'M', 'ョ'), + (0xFF6F, 'M', 'ッ'), + (0xFF70, 'M', 'ー'), + (0xFF71, 'M', 'ア'), + (0xFF72, 'M', 'イ'), + (0xFF73, 'M', 'ウ'), + (0xFF74, 'M', 'エ'), + (0xFF75, 'M', 'オ'), + (0xFF76, 'M', 'カ'), + (0xFF77, 'M', 'キ'), + (0xFF78, 'M', 'ク'), + (0xFF79, 'M', 'ケ'), + (0xFF7A, 'M', 'コ'), + (0xFF7B, 'M', 'サ'), + (0xFF7C, 'M', 'シ'), + (0xFF7D, 'M', 'ス'), + (0xFF7E, 'M', 'セ'), + ] + +def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFF7F, 'M', 'ソ'), + (0xFF80, 'M', 'タ'), + (0xFF81, 'M', 'チ'), + (0xFF82, 'M', 'ツ'), + (0xFF83, 'M', 'テ'), + (0xFF84, 'M', 'ト'), + (0xFF85, 'M', 'ナ'), + (0xFF86, 'M', 'ニ'), + (0xFF87, 'M', 'ヌ'), + (0xFF88, 'M', 'ネ'), + (0xFF89, 'M', 'ノ'), + (0xFF8A, 'M', 'ハ'), + (0xFF8B, 'M', 'ヒ'), + (0xFF8C, 'M', 'フ'), + (0xFF8D, 'M', 'ヘ'), + (0xFF8E, 'M', 'ホ'), + (0xFF8F, 'M', 'マ'), + (0xFF90, 'M', 'ミ'), + (0xFF91, 'M', 'ム'), + (0xFF92, 'M', 'メ'), + (0xFF93, 'M', 'モ'), + (0xFF94, 'M', 'ヤ'), + (0xFF95, 'M', 'ユ'), + (0xFF96, 'M', 'ヨ'), + (0xFF97, 'M', 'ラ'), + (0xFF98, 'M', 'リ'), + (0xFF99, 'M', 'ル'), + (0xFF9A, 'M', 'レ'), + (0xFF9B, 'M', 'ロ'), + (0xFF9C, 'M', 'ワ'), + (0xFF9D, 'M', 'ン'), + (0xFF9E, 'M', '゙'), + (0xFF9F, 'M', '゚'), + (0xFFA0, 'X'), + (0xFFA1, 'M', 'ᄀ'), + (0xFFA2, 'M', 'ᄁ'), + (0xFFA3, 'M', 'ᆪ'), + (0xFFA4, 'M', 'ᄂ'), + (0xFFA5, 'M', 'ᆬ'), + (0xFFA6, 'M', 'ᆭ'), + (0xFFA7, 'M', 'ᄃ'), + (0xFFA8, 'M', 'ᄄ'), + (0xFFA9, 'M', 'ᄅ'), + (0xFFAA, 'M', 'ᆰ'), + (0xFFAB, 'M', 'ᆱ'), + (0xFFAC, 'M', 'ᆲ'), + (0xFFAD, 'M', 'ᆳ'), + (0xFFAE, 'M', 'ᆴ'), + (0xFFAF, 'M', 'ᆵ'), + (0xFFB0, 'M', 'ᄚ'), + (0xFFB1, 'M', 'ᄆ'), + (0xFFB2, 'M', 'ᄇ'), + (0xFFB3, 'M', 'ᄈ'), + (0xFFB4, 'M', 'ᄡ'), + (0xFFB5, 'M', 'ᄉ'), + (0xFFB6, 'M', 'ᄊ'), + (0xFFB7, 'M', 'ᄋ'), + (0xFFB8, 'M', 'ᄌ'), + (0xFFB9, 'M', 'ᄍ'), + (0xFFBA, 'M', 'ᄎ'), + (0xFFBB, 'M', 'ᄏ'), + (0xFFBC, 'M', 'ᄐ'), + (0xFFBD, 'M', 'ᄑ'), + (0xFFBE, 'M', 'ᄒ'), + (0xFFBF, 'X'), + (0xFFC2, 'M', 'ᅡ'), + (0xFFC3, 'M', 'ᅢ'), + (0xFFC4, 'M', 'ᅣ'), + (0xFFC5, 'M', 'ᅤ'), + (0xFFC6, 'M', 'ᅥ'), + (0xFFC7, 'M', 'ᅦ'), + (0xFFC8, 'X'), + (0xFFCA, 'M', 'ᅧ'), + (0xFFCB, 'M', 'ᅨ'), + (0xFFCC, 'M', 'ᅩ'), + (0xFFCD, 'M', 'ᅪ'), + (0xFFCE, 'M', 'ᅫ'), + (0xFFCF, 'M', 'ᅬ'), + (0xFFD0, 'X'), + (0xFFD2, 'M', 'ᅭ'), + (0xFFD3, 'M', 'ᅮ'), + (0xFFD4, 'M', 'ᅯ'), + (0xFFD5, 'M', 'ᅰ'), + (0xFFD6, 'M', 'ᅱ'), + (0xFFD7, 'M', 'ᅲ'), + (0xFFD8, 'X'), + (0xFFDA, 'M', 'ᅳ'), + (0xFFDB, 'M', 'ᅴ'), + (0xFFDC, 'M', 'ᅵ'), + (0xFFDD, 'X'), + (0xFFE0, 'M', '¢'), + (0xFFE1, 'M', '£'), + (0xFFE2, 'M', '¬'), + (0xFFE3, '3', ' ̄'), + (0xFFE4, 'M', '¦'), + (0xFFE5, 'M', '¥'), + (0xFFE6, 'M', '₩'), + (0xFFE7, 'X'), + (0xFFE8, 'M', '│'), + (0xFFE9, 'M', '←'), + ] + +def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFFEA, 'M', '↑'), + (0xFFEB, 'M', '→'), + (0xFFEC, 'M', '↓'), + (0xFFED, 'M', '■'), + (0xFFEE, 'M', '○'), + (0xFFEF, 'X'), + (0x10000, 'V'), + (0x1000C, 'X'), + (0x1000D, 'V'), + (0x10027, 'X'), + (0x10028, 'V'), + (0x1003B, 'X'), + (0x1003C, 'V'), + (0x1003E, 'X'), + (0x1003F, 'V'), + (0x1004E, 'X'), + (0x10050, 'V'), + (0x1005E, 'X'), + (0x10080, 'V'), + (0x100FB, 'X'), + (0x10100, 'V'), + (0x10103, 'X'), + (0x10107, 'V'), + (0x10134, 'X'), + (0x10137, 'V'), + (0x1018F, 'X'), + (0x10190, 'V'), + (0x1019D, 'X'), + (0x101A0, 'V'), + (0x101A1, 'X'), + (0x101D0, 'V'), + (0x101FE, 'X'), + (0x10280, 'V'), + (0x1029D, 'X'), + (0x102A0, 'V'), + (0x102D1, 'X'), + (0x102E0, 'V'), + (0x102FC, 'X'), + (0x10300, 'V'), + (0x10324, 'X'), + (0x1032D, 'V'), + (0x1034B, 'X'), + (0x10350, 'V'), + (0x1037B, 'X'), + (0x10380, 'V'), + (0x1039E, 'X'), + (0x1039F, 'V'), + (0x103C4, 'X'), + (0x103C8, 'V'), + (0x103D6, 'X'), + (0x10400, 'M', '𐐨'), + (0x10401, 'M', '𐐩'), + (0x10402, 'M', '𐐪'), + (0x10403, 'M', '𐐫'), + (0x10404, 'M', '𐐬'), + (0x10405, 'M', '𐐭'), + (0x10406, 'M', '𐐮'), + (0x10407, 'M', '𐐯'), + (0x10408, 'M', '𐐰'), + (0x10409, 'M', '𐐱'), + (0x1040A, 'M', '𐐲'), + (0x1040B, 'M', '𐐳'), + (0x1040C, 'M', '𐐴'), + (0x1040D, 'M', '𐐵'), + (0x1040E, 'M', '𐐶'), + (0x1040F, 'M', '𐐷'), + (0x10410, 'M', '𐐸'), + (0x10411, 'M', '𐐹'), + (0x10412, 'M', '𐐺'), + (0x10413, 'M', '𐐻'), + (0x10414, 'M', '𐐼'), + (0x10415, 'M', '𐐽'), + (0x10416, 'M', '𐐾'), + (0x10417, 'M', '𐐿'), + (0x10418, 'M', '𐑀'), + (0x10419, 'M', '𐑁'), + (0x1041A, 'M', '𐑂'), + (0x1041B, 'M', '𐑃'), + (0x1041C, 'M', '𐑄'), + (0x1041D, 'M', '𐑅'), + (0x1041E, 'M', '𐑆'), + (0x1041F, 'M', '𐑇'), + (0x10420, 'M', '𐑈'), + (0x10421, 'M', '𐑉'), + (0x10422, 'M', '𐑊'), + (0x10423, 'M', '𐑋'), + (0x10424, 'M', '𐑌'), + (0x10425, 'M', '𐑍'), + (0x10426, 'M', '𐑎'), + (0x10427, 'M', '𐑏'), + (0x10428, 'V'), + (0x1049E, 'X'), + (0x104A0, 'V'), + (0x104AA, 'X'), + (0x104B0, 'M', '𐓘'), + (0x104B1, 'M', '𐓙'), + (0x104B2, 'M', '𐓚'), + (0x104B3, 'M', '𐓛'), + (0x104B4, 'M', '𐓜'), + (0x104B5, 'M', '𐓝'), + ] + +def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x104B6, 'M', '𐓞'), + (0x104B7, 'M', '𐓟'), + (0x104B8, 'M', '𐓠'), + (0x104B9, 'M', '𐓡'), + (0x104BA, 'M', '𐓢'), + (0x104BB, 'M', '𐓣'), + (0x104BC, 'M', '𐓤'), + (0x104BD, 'M', '𐓥'), + (0x104BE, 'M', '𐓦'), + (0x104BF, 'M', '𐓧'), + (0x104C0, 'M', '𐓨'), + (0x104C1, 'M', '𐓩'), + (0x104C2, 'M', '𐓪'), + (0x104C3, 'M', '𐓫'), + (0x104C4, 'M', '𐓬'), + (0x104C5, 'M', '𐓭'), + (0x104C6, 'M', '𐓮'), + (0x104C7, 'M', '𐓯'), + (0x104C8, 'M', '𐓰'), + (0x104C9, 'M', '𐓱'), + (0x104CA, 'M', '𐓲'), + (0x104CB, 'M', '𐓳'), + (0x104CC, 'M', '𐓴'), + (0x104CD, 'M', '𐓵'), + (0x104CE, 'M', '𐓶'), + (0x104CF, 'M', '𐓷'), + (0x104D0, 'M', '𐓸'), + (0x104D1, 'M', '𐓹'), + (0x104D2, 'M', '𐓺'), + (0x104D3, 'M', '𐓻'), + (0x104D4, 'X'), + (0x104D8, 'V'), + (0x104FC, 'X'), + (0x10500, 'V'), + (0x10528, 'X'), + (0x10530, 'V'), + (0x10564, 'X'), + (0x1056F, 'V'), + (0x10570, 'M', '𐖗'), + (0x10571, 'M', '𐖘'), + (0x10572, 'M', '𐖙'), + (0x10573, 'M', '𐖚'), + (0x10574, 'M', '𐖛'), + (0x10575, 'M', '𐖜'), + (0x10576, 'M', '𐖝'), + (0x10577, 'M', '𐖞'), + (0x10578, 'M', '𐖟'), + (0x10579, 'M', '𐖠'), + (0x1057A, 'M', '𐖡'), + (0x1057B, 'X'), + (0x1057C, 'M', '𐖣'), + (0x1057D, 'M', '𐖤'), + (0x1057E, 'M', '𐖥'), + (0x1057F, 'M', '𐖦'), + (0x10580, 'M', '𐖧'), + (0x10581, 'M', '𐖨'), + (0x10582, 'M', '𐖩'), + (0x10583, 'M', '𐖪'), + (0x10584, 'M', '𐖫'), + (0x10585, 'M', '𐖬'), + (0x10586, 'M', '𐖭'), + (0x10587, 'M', '𐖮'), + (0x10588, 'M', '𐖯'), + (0x10589, 'M', '𐖰'), + (0x1058A, 'M', '𐖱'), + (0x1058B, 'X'), + (0x1058C, 'M', '𐖳'), + (0x1058D, 'M', '𐖴'), + (0x1058E, 'M', '𐖵'), + (0x1058F, 'M', '𐖶'), + (0x10590, 'M', '𐖷'), + (0x10591, 'M', '𐖸'), + (0x10592, 'M', '𐖹'), + (0x10593, 'X'), + (0x10594, 'M', '𐖻'), + (0x10595, 'M', '𐖼'), + (0x10596, 'X'), + (0x10597, 'V'), + (0x105A2, 'X'), + (0x105A3, 'V'), + (0x105B2, 'X'), + (0x105B3, 'V'), + (0x105BA, 'X'), + (0x105BB, 'V'), + (0x105BD, 'X'), + (0x10600, 'V'), + (0x10737, 'X'), + (0x10740, 'V'), + (0x10756, 'X'), + (0x10760, 'V'), + (0x10768, 'X'), + (0x10780, 'V'), + (0x10781, 'M', 'ː'), + (0x10782, 'M', 'ˑ'), + (0x10783, 'M', 'æ'), + (0x10784, 'M', 'ʙ'), + (0x10785, 'M', 'ɓ'), + (0x10786, 'X'), + (0x10787, 'M', 'ʣ'), + (0x10788, 'M', 'ꭦ'), + ] + +def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x10789, 'M', 'ʥ'), + (0x1078A, 'M', 'ʤ'), + (0x1078B, 'M', 'ɖ'), + (0x1078C, 'M', 'ɗ'), + (0x1078D, 'M', 'ᶑ'), + (0x1078E, 'M', 'ɘ'), + (0x1078F, 'M', 'ɞ'), + (0x10790, 'M', 'ʩ'), + (0x10791, 'M', 'ɤ'), + (0x10792, 'M', 'ɢ'), + (0x10793, 'M', 'ɠ'), + (0x10794, 'M', 'ʛ'), + (0x10795, 'M', 'ħ'), + (0x10796, 'M', 'ʜ'), + (0x10797, 'M', 'ɧ'), + (0x10798, 'M', 'ʄ'), + (0x10799, 'M', 'ʪ'), + (0x1079A, 'M', 'ʫ'), + (0x1079B, 'M', 'ɬ'), + (0x1079C, 'M', '𝼄'), + (0x1079D, 'M', 'ꞎ'), + (0x1079E, 'M', 'ɮ'), + (0x1079F, 'M', '𝼅'), + (0x107A0, 'M', 'ʎ'), + (0x107A1, 'M', '𝼆'), + (0x107A2, 'M', 'ø'), + (0x107A3, 'M', 'ɶ'), + (0x107A4, 'M', 'ɷ'), + (0x107A5, 'M', 'q'), + (0x107A6, 'M', 'ɺ'), + (0x107A7, 'M', '𝼈'), + (0x107A8, 'M', 'ɽ'), + (0x107A9, 'M', 'ɾ'), + (0x107AA, 'M', 'ʀ'), + (0x107AB, 'M', 'ʨ'), + (0x107AC, 'M', 'ʦ'), + (0x107AD, 'M', 'ꭧ'), + (0x107AE, 'M', 'ʧ'), + (0x107AF, 'M', 'ʈ'), + (0x107B0, 'M', 'ⱱ'), + (0x107B1, 'X'), + (0x107B2, 'M', 'ʏ'), + (0x107B3, 'M', 'ʡ'), + (0x107B4, 'M', 'ʢ'), + (0x107B5, 'M', 'ʘ'), + (0x107B6, 'M', 'ǀ'), + (0x107B7, 'M', 'ǁ'), + (0x107B8, 'M', 'ǂ'), + (0x107B9, 'M', '𝼊'), + (0x107BA, 'M', '𝼞'), + (0x107BB, 'X'), + (0x10800, 'V'), + (0x10806, 'X'), + (0x10808, 'V'), + (0x10809, 'X'), + (0x1080A, 'V'), + (0x10836, 'X'), + (0x10837, 'V'), + (0x10839, 'X'), + (0x1083C, 'V'), + (0x1083D, 'X'), + (0x1083F, 'V'), + (0x10856, 'X'), + (0x10857, 'V'), + (0x1089F, 'X'), + (0x108A7, 'V'), + (0x108B0, 'X'), + (0x108E0, 'V'), + (0x108F3, 'X'), + (0x108F4, 'V'), + (0x108F6, 'X'), + (0x108FB, 'V'), + (0x1091C, 'X'), + (0x1091F, 'V'), + (0x1093A, 'X'), + (0x1093F, 'V'), + (0x10940, 'X'), + (0x10980, 'V'), + (0x109B8, 'X'), + (0x109BC, 'V'), + (0x109D0, 'X'), + (0x109D2, 'V'), + (0x10A04, 'X'), + (0x10A05, 'V'), + (0x10A07, 'X'), + (0x10A0C, 'V'), + (0x10A14, 'X'), + (0x10A15, 'V'), + (0x10A18, 'X'), + (0x10A19, 'V'), + (0x10A36, 'X'), + (0x10A38, 'V'), + (0x10A3B, 'X'), + (0x10A3F, 'V'), + (0x10A49, 'X'), + (0x10A50, 'V'), + (0x10A59, 'X'), + (0x10A60, 'V'), + (0x10AA0, 'X'), + (0x10AC0, 'V'), + ] + +def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x10AE7, 'X'), + (0x10AEB, 'V'), + (0x10AF7, 'X'), + (0x10B00, 'V'), + (0x10B36, 'X'), + (0x10B39, 'V'), + (0x10B56, 'X'), + (0x10B58, 'V'), + (0x10B73, 'X'), + (0x10B78, 'V'), + (0x10B92, 'X'), + (0x10B99, 'V'), + (0x10B9D, 'X'), + (0x10BA9, 'V'), + (0x10BB0, 'X'), + (0x10C00, 'V'), + (0x10C49, 'X'), + (0x10C80, 'M', '𐳀'), + (0x10C81, 'M', '𐳁'), + (0x10C82, 'M', '𐳂'), + (0x10C83, 'M', '𐳃'), + (0x10C84, 'M', '𐳄'), + (0x10C85, 'M', '𐳅'), + (0x10C86, 'M', '𐳆'), + (0x10C87, 'M', '𐳇'), + (0x10C88, 'M', '𐳈'), + (0x10C89, 'M', '𐳉'), + (0x10C8A, 'M', '𐳊'), + (0x10C8B, 'M', '𐳋'), + (0x10C8C, 'M', '𐳌'), + (0x10C8D, 'M', '𐳍'), + (0x10C8E, 'M', '𐳎'), + (0x10C8F, 'M', '𐳏'), + (0x10C90, 'M', '𐳐'), + (0x10C91, 'M', '𐳑'), + (0x10C92, 'M', '𐳒'), + (0x10C93, 'M', '𐳓'), + (0x10C94, 'M', '𐳔'), + (0x10C95, 'M', '𐳕'), + (0x10C96, 'M', '𐳖'), + (0x10C97, 'M', '𐳗'), + (0x10C98, 'M', '𐳘'), + (0x10C99, 'M', '𐳙'), + (0x10C9A, 'M', '𐳚'), + (0x10C9B, 'M', '𐳛'), + (0x10C9C, 'M', '𐳜'), + (0x10C9D, 'M', '𐳝'), + (0x10C9E, 'M', '𐳞'), + (0x10C9F, 'M', '𐳟'), + (0x10CA0, 'M', '𐳠'), + (0x10CA1, 'M', '𐳡'), + (0x10CA2, 'M', '𐳢'), + (0x10CA3, 'M', '𐳣'), + (0x10CA4, 'M', '𐳤'), + (0x10CA5, 'M', '𐳥'), + (0x10CA6, 'M', '𐳦'), + (0x10CA7, 'M', '𐳧'), + (0x10CA8, 'M', '𐳨'), + (0x10CA9, 'M', '𐳩'), + (0x10CAA, 'M', '𐳪'), + (0x10CAB, 'M', '𐳫'), + (0x10CAC, 'M', '𐳬'), + (0x10CAD, 'M', '𐳭'), + (0x10CAE, 'M', '𐳮'), + (0x10CAF, 'M', '𐳯'), + (0x10CB0, 'M', '𐳰'), + (0x10CB1, 'M', '𐳱'), + (0x10CB2, 'M', '𐳲'), + (0x10CB3, 'X'), + (0x10CC0, 'V'), + (0x10CF3, 'X'), + (0x10CFA, 'V'), + (0x10D28, 'X'), + (0x10D30, 'V'), + (0x10D3A, 'X'), + (0x10E60, 'V'), + (0x10E7F, 'X'), + (0x10E80, 'V'), + (0x10EAA, 'X'), + (0x10EAB, 'V'), + (0x10EAE, 'X'), + (0x10EB0, 'V'), + (0x10EB2, 'X'), + (0x10EFD, 'V'), + (0x10F28, 'X'), + (0x10F30, 'V'), + (0x10F5A, 'X'), + (0x10F70, 'V'), + (0x10F8A, 'X'), + (0x10FB0, 'V'), + (0x10FCC, 'X'), + (0x10FE0, 'V'), + (0x10FF7, 'X'), + (0x11000, 'V'), + (0x1104E, 'X'), + (0x11052, 'V'), + (0x11076, 'X'), + (0x1107F, 'V'), + (0x110BD, 'X'), + (0x110BE, 'V'), + ] + +def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x110C3, 'X'), + (0x110D0, 'V'), + (0x110E9, 'X'), + (0x110F0, 'V'), + (0x110FA, 'X'), + (0x11100, 'V'), + (0x11135, 'X'), + (0x11136, 'V'), + (0x11148, 'X'), + (0x11150, 'V'), + (0x11177, 'X'), + (0x11180, 'V'), + (0x111E0, 'X'), + (0x111E1, 'V'), + (0x111F5, 'X'), + (0x11200, 'V'), + (0x11212, 'X'), + (0x11213, 'V'), + (0x11242, 'X'), + (0x11280, 'V'), + (0x11287, 'X'), + (0x11288, 'V'), + (0x11289, 'X'), + (0x1128A, 'V'), + (0x1128E, 'X'), + (0x1128F, 'V'), + (0x1129E, 'X'), + (0x1129F, 'V'), + (0x112AA, 'X'), + (0x112B0, 'V'), + (0x112EB, 'X'), + (0x112F0, 'V'), + (0x112FA, 'X'), + (0x11300, 'V'), + (0x11304, 'X'), + (0x11305, 'V'), + (0x1130D, 'X'), + (0x1130F, 'V'), + (0x11311, 'X'), + (0x11313, 'V'), + (0x11329, 'X'), + (0x1132A, 'V'), + (0x11331, 'X'), + (0x11332, 'V'), + (0x11334, 'X'), + (0x11335, 'V'), + (0x1133A, 'X'), + (0x1133B, 'V'), + (0x11345, 'X'), + (0x11347, 'V'), + (0x11349, 'X'), + (0x1134B, 'V'), + (0x1134E, 'X'), + (0x11350, 'V'), + (0x11351, 'X'), + (0x11357, 'V'), + (0x11358, 'X'), + (0x1135D, 'V'), + (0x11364, 'X'), + (0x11366, 'V'), + (0x1136D, 'X'), + (0x11370, 'V'), + (0x11375, 'X'), + (0x11400, 'V'), + (0x1145C, 'X'), + (0x1145D, 'V'), + (0x11462, 'X'), + (0x11480, 'V'), + (0x114C8, 'X'), + (0x114D0, 'V'), + (0x114DA, 'X'), + (0x11580, 'V'), + (0x115B6, 'X'), + (0x115B8, 'V'), + (0x115DE, 'X'), + (0x11600, 'V'), + (0x11645, 'X'), + (0x11650, 'V'), + (0x1165A, 'X'), + (0x11660, 'V'), + (0x1166D, 'X'), + (0x11680, 'V'), + (0x116BA, 'X'), + (0x116C0, 'V'), + (0x116CA, 'X'), + (0x11700, 'V'), + (0x1171B, 'X'), + (0x1171D, 'V'), + (0x1172C, 'X'), + (0x11730, 'V'), + (0x11747, 'X'), + (0x11800, 'V'), + (0x1183C, 'X'), + (0x118A0, 'M', '𑣀'), + (0x118A1, 'M', '𑣁'), + (0x118A2, 'M', '𑣂'), + (0x118A3, 'M', '𑣃'), + (0x118A4, 'M', '𑣄'), + (0x118A5, 'M', '𑣅'), + (0x118A6, 'M', '𑣆'), + ] + +def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x118A7, 'M', '𑣇'), + (0x118A8, 'M', '𑣈'), + (0x118A9, 'M', '𑣉'), + (0x118AA, 'M', '𑣊'), + (0x118AB, 'M', '𑣋'), + (0x118AC, 'M', '𑣌'), + (0x118AD, 'M', '𑣍'), + (0x118AE, 'M', '𑣎'), + (0x118AF, 'M', '𑣏'), + (0x118B0, 'M', '𑣐'), + (0x118B1, 'M', '𑣑'), + (0x118B2, 'M', '𑣒'), + (0x118B3, 'M', '𑣓'), + (0x118B4, 'M', '𑣔'), + (0x118B5, 'M', '𑣕'), + (0x118B6, 'M', '𑣖'), + (0x118B7, 'M', '𑣗'), + (0x118B8, 'M', '𑣘'), + (0x118B9, 'M', '𑣙'), + (0x118BA, 'M', '𑣚'), + (0x118BB, 'M', '𑣛'), + (0x118BC, 'M', '𑣜'), + (0x118BD, 'M', '𑣝'), + (0x118BE, 'M', '𑣞'), + (0x118BF, 'M', '𑣟'), + (0x118C0, 'V'), + (0x118F3, 'X'), + (0x118FF, 'V'), + (0x11907, 'X'), + (0x11909, 'V'), + (0x1190A, 'X'), + (0x1190C, 'V'), + (0x11914, 'X'), + (0x11915, 'V'), + (0x11917, 'X'), + (0x11918, 'V'), + (0x11936, 'X'), + (0x11937, 'V'), + (0x11939, 'X'), + (0x1193B, 'V'), + (0x11947, 'X'), + (0x11950, 'V'), + (0x1195A, 'X'), + (0x119A0, 'V'), + (0x119A8, 'X'), + (0x119AA, 'V'), + (0x119D8, 'X'), + (0x119DA, 'V'), + (0x119E5, 'X'), + (0x11A00, 'V'), + (0x11A48, 'X'), + (0x11A50, 'V'), + (0x11AA3, 'X'), + (0x11AB0, 'V'), + (0x11AF9, 'X'), + (0x11B00, 'V'), + (0x11B0A, 'X'), + (0x11C00, 'V'), + (0x11C09, 'X'), + (0x11C0A, 'V'), + (0x11C37, 'X'), + (0x11C38, 'V'), + (0x11C46, 'X'), + (0x11C50, 'V'), + (0x11C6D, 'X'), + (0x11C70, 'V'), + (0x11C90, 'X'), + (0x11C92, 'V'), + (0x11CA8, 'X'), + (0x11CA9, 'V'), + (0x11CB7, 'X'), + (0x11D00, 'V'), + (0x11D07, 'X'), + (0x11D08, 'V'), + (0x11D0A, 'X'), + (0x11D0B, 'V'), + (0x11D37, 'X'), + (0x11D3A, 'V'), + (0x11D3B, 'X'), + (0x11D3C, 'V'), + (0x11D3E, 'X'), + (0x11D3F, 'V'), + (0x11D48, 'X'), + (0x11D50, 'V'), + (0x11D5A, 'X'), + (0x11D60, 'V'), + (0x11D66, 'X'), + (0x11D67, 'V'), + (0x11D69, 'X'), + (0x11D6A, 'V'), + (0x11D8F, 'X'), + (0x11D90, 'V'), + (0x11D92, 'X'), + (0x11D93, 'V'), + (0x11D99, 'X'), + (0x11DA0, 'V'), + (0x11DAA, 'X'), + (0x11EE0, 'V'), + (0x11EF9, 'X'), + (0x11F00, 'V'), + ] + +def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x11F11, 'X'), + (0x11F12, 'V'), + (0x11F3B, 'X'), + (0x11F3E, 'V'), + (0x11F5A, 'X'), + (0x11FB0, 'V'), + (0x11FB1, 'X'), + (0x11FC0, 'V'), + (0x11FF2, 'X'), + (0x11FFF, 'V'), + (0x1239A, 'X'), + (0x12400, 'V'), + (0x1246F, 'X'), + (0x12470, 'V'), + (0x12475, 'X'), + (0x12480, 'V'), + (0x12544, 'X'), + (0x12F90, 'V'), + (0x12FF3, 'X'), + (0x13000, 'V'), + (0x13430, 'X'), + (0x13440, 'V'), + (0x13456, 'X'), + (0x14400, 'V'), + (0x14647, 'X'), + (0x16800, 'V'), + (0x16A39, 'X'), + (0x16A40, 'V'), + (0x16A5F, 'X'), + (0x16A60, 'V'), + (0x16A6A, 'X'), + (0x16A6E, 'V'), + (0x16ABF, 'X'), + (0x16AC0, 'V'), + (0x16ACA, 'X'), + (0x16AD0, 'V'), + (0x16AEE, 'X'), + (0x16AF0, 'V'), + (0x16AF6, 'X'), + (0x16B00, 'V'), + (0x16B46, 'X'), + (0x16B50, 'V'), + (0x16B5A, 'X'), + (0x16B5B, 'V'), + (0x16B62, 'X'), + (0x16B63, 'V'), + (0x16B78, 'X'), + (0x16B7D, 'V'), + (0x16B90, 'X'), + (0x16E40, 'M', '𖹠'), + (0x16E41, 'M', '𖹡'), + (0x16E42, 'M', '𖹢'), + (0x16E43, 'M', '𖹣'), + (0x16E44, 'M', '𖹤'), + (0x16E45, 'M', '𖹥'), + (0x16E46, 'M', '𖹦'), + (0x16E47, 'M', '𖹧'), + (0x16E48, 'M', '𖹨'), + (0x16E49, 'M', '𖹩'), + (0x16E4A, 'M', '𖹪'), + (0x16E4B, 'M', '𖹫'), + (0x16E4C, 'M', '𖹬'), + (0x16E4D, 'M', '𖹭'), + (0x16E4E, 'M', '𖹮'), + (0x16E4F, 'M', '𖹯'), + (0x16E50, 'M', '𖹰'), + (0x16E51, 'M', '𖹱'), + (0x16E52, 'M', '𖹲'), + (0x16E53, 'M', '𖹳'), + (0x16E54, 'M', '𖹴'), + (0x16E55, 'M', '𖹵'), + (0x16E56, 'M', '𖹶'), + (0x16E57, 'M', '𖹷'), + (0x16E58, 'M', '𖹸'), + (0x16E59, 'M', '𖹹'), + (0x16E5A, 'M', '𖹺'), + (0x16E5B, 'M', '𖹻'), + (0x16E5C, 'M', '𖹼'), + (0x16E5D, 'M', '𖹽'), + (0x16E5E, 'M', '𖹾'), + (0x16E5F, 'M', '𖹿'), + (0x16E60, 'V'), + (0x16E9B, 'X'), + (0x16F00, 'V'), + (0x16F4B, 'X'), + (0x16F4F, 'V'), + (0x16F88, 'X'), + (0x16F8F, 'V'), + (0x16FA0, 'X'), + (0x16FE0, 'V'), + (0x16FE5, 'X'), + (0x16FF0, 'V'), + (0x16FF2, 'X'), + (0x17000, 'V'), + (0x187F8, 'X'), + (0x18800, 'V'), + (0x18CD6, 'X'), + (0x18D00, 'V'), + (0x18D09, 'X'), + (0x1AFF0, 'V'), + ] + +def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1AFF4, 'X'), + (0x1AFF5, 'V'), + (0x1AFFC, 'X'), + (0x1AFFD, 'V'), + (0x1AFFF, 'X'), + (0x1B000, 'V'), + (0x1B123, 'X'), + (0x1B132, 'V'), + (0x1B133, 'X'), + (0x1B150, 'V'), + (0x1B153, 'X'), + (0x1B155, 'V'), + (0x1B156, 'X'), + (0x1B164, 'V'), + (0x1B168, 'X'), + (0x1B170, 'V'), + (0x1B2FC, 'X'), + (0x1BC00, 'V'), + (0x1BC6B, 'X'), + (0x1BC70, 'V'), + (0x1BC7D, 'X'), + (0x1BC80, 'V'), + (0x1BC89, 'X'), + (0x1BC90, 'V'), + (0x1BC9A, 'X'), + (0x1BC9C, 'V'), + (0x1BCA0, 'I'), + (0x1BCA4, 'X'), + (0x1CF00, 'V'), + (0x1CF2E, 'X'), + (0x1CF30, 'V'), + (0x1CF47, 'X'), + (0x1CF50, 'V'), + (0x1CFC4, 'X'), + (0x1D000, 'V'), + (0x1D0F6, 'X'), + (0x1D100, 'V'), + (0x1D127, 'X'), + (0x1D129, 'V'), + (0x1D15E, 'M', '𝅗𝅥'), + (0x1D15F, 'M', '𝅘𝅥'), + (0x1D160, 'M', '𝅘𝅥𝅮'), + (0x1D161, 'M', '𝅘𝅥𝅯'), + (0x1D162, 'M', '𝅘𝅥𝅰'), + (0x1D163, 'M', '𝅘𝅥𝅱'), + (0x1D164, 'M', '𝅘𝅥𝅲'), + (0x1D165, 'V'), + (0x1D173, 'X'), + (0x1D17B, 'V'), + (0x1D1BB, 'M', '𝆹𝅥'), + (0x1D1BC, 'M', '𝆺𝅥'), + (0x1D1BD, 'M', '𝆹𝅥𝅮'), + (0x1D1BE, 'M', '𝆺𝅥𝅮'), + (0x1D1BF, 'M', '𝆹𝅥𝅯'), + (0x1D1C0, 'M', '𝆺𝅥𝅯'), + (0x1D1C1, 'V'), + (0x1D1EB, 'X'), + (0x1D200, 'V'), + (0x1D246, 'X'), + (0x1D2C0, 'V'), + (0x1D2D4, 'X'), + (0x1D2E0, 'V'), + (0x1D2F4, 'X'), + (0x1D300, 'V'), + (0x1D357, 'X'), + (0x1D360, 'V'), + (0x1D379, 'X'), + (0x1D400, 'M', 'a'), + (0x1D401, 'M', 'b'), + (0x1D402, 'M', 'c'), + (0x1D403, 'M', 'd'), + (0x1D404, 'M', 'e'), + (0x1D405, 'M', 'f'), + (0x1D406, 'M', 'g'), + (0x1D407, 'M', 'h'), + (0x1D408, 'M', 'i'), + (0x1D409, 'M', 'j'), + (0x1D40A, 'M', 'k'), + (0x1D40B, 'M', 'l'), + (0x1D40C, 'M', 'm'), + (0x1D40D, 'M', 'n'), + (0x1D40E, 'M', 'o'), + (0x1D40F, 'M', 'p'), + (0x1D410, 'M', 'q'), + (0x1D411, 'M', 'r'), + (0x1D412, 'M', 's'), + (0x1D413, 'M', 't'), + (0x1D414, 'M', 'u'), + (0x1D415, 'M', 'v'), + (0x1D416, 'M', 'w'), + (0x1D417, 'M', 'x'), + (0x1D418, 'M', 'y'), + (0x1D419, 'M', 'z'), + (0x1D41A, 'M', 'a'), + (0x1D41B, 'M', 'b'), + (0x1D41C, 'M', 'c'), + (0x1D41D, 'M', 'd'), + (0x1D41E, 'M', 'e'), + (0x1D41F, 'M', 'f'), + (0x1D420, 'M', 'g'), + ] + +def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D421, 'M', 'h'), + (0x1D422, 'M', 'i'), + (0x1D423, 'M', 'j'), + (0x1D424, 'M', 'k'), + (0x1D425, 'M', 'l'), + (0x1D426, 'M', 'm'), + (0x1D427, 'M', 'n'), + (0x1D428, 'M', 'o'), + (0x1D429, 'M', 'p'), + (0x1D42A, 'M', 'q'), + (0x1D42B, 'M', 'r'), + (0x1D42C, 'M', 's'), + (0x1D42D, 'M', 't'), + (0x1D42E, 'M', 'u'), + (0x1D42F, 'M', 'v'), + (0x1D430, 'M', 'w'), + (0x1D431, 'M', 'x'), + (0x1D432, 'M', 'y'), + (0x1D433, 'M', 'z'), + (0x1D434, 'M', 'a'), + (0x1D435, 'M', 'b'), + (0x1D436, 'M', 'c'), + (0x1D437, 'M', 'd'), + (0x1D438, 'M', 'e'), + (0x1D439, 'M', 'f'), + (0x1D43A, 'M', 'g'), + (0x1D43B, 'M', 'h'), + (0x1D43C, 'M', 'i'), + (0x1D43D, 'M', 'j'), + (0x1D43E, 'M', 'k'), + (0x1D43F, 'M', 'l'), + (0x1D440, 'M', 'm'), + (0x1D441, 'M', 'n'), + (0x1D442, 'M', 'o'), + (0x1D443, 'M', 'p'), + (0x1D444, 'M', 'q'), + (0x1D445, 'M', 'r'), + (0x1D446, 'M', 's'), + (0x1D447, 'M', 't'), + (0x1D448, 'M', 'u'), + (0x1D449, 'M', 'v'), + (0x1D44A, 'M', 'w'), + (0x1D44B, 'M', 'x'), + (0x1D44C, 'M', 'y'), + (0x1D44D, 'M', 'z'), + (0x1D44E, 'M', 'a'), + (0x1D44F, 'M', 'b'), + (0x1D450, 'M', 'c'), + (0x1D451, 'M', 'd'), + (0x1D452, 'M', 'e'), + (0x1D453, 'M', 'f'), + (0x1D454, 'M', 'g'), + (0x1D455, 'X'), + (0x1D456, 'M', 'i'), + (0x1D457, 'M', 'j'), + (0x1D458, 'M', 'k'), + (0x1D459, 'M', 'l'), + (0x1D45A, 'M', 'm'), + (0x1D45B, 'M', 'n'), + (0x1D45C, 'M', 'o'), + (0x1D45D, 'M', 'p'), + (0x1D45E, 'M', 'q'), + (0x1D45F, 'M', 'r'), + (0x1D460, 'M', 's'), + (0x1D461, 'M', 't'), + (0x1D462, 'M', 'u'), + (0x1D463, 'M', 'v'), + (0x1D464, 'M', 'w'), + (0x1D465, 'M', 'x'), + (0x1D466, 'M', 'y'), + (0x1D467, 'M', 'z'), + (0x1D468, 'M', 'a'), + (0x1D469, 'M', 'b'), + (0x1D46A, 'M', 'c'), + (0x1D46B, 'M', 'd'), + (0x1D46C, 'M', 'e'), + (0x1D46D, 'M', 'f'), + (0x1D46E, 'M', 'g'), + (0x1D46F, 'M', 'h'), + (0x1D470, 'M', 'i'), + (0x1D471, 'M', 'j'), + (0x1D472, 'M', 'k'), + (0x1D473, 'M', 'l'), + (0x1D474, 'M', 'm'), + (0x1D475, 'M', 'n'), + (0x1D476, 'M', 'o'), + (0x1D477, 'M', 'p'), + (0x1D478, 'M', 'q'), + (0x1D479, 'M', 'r'), + (0x1D47A, 'M', 's'), + (0x1D47B, 'M', 't'), + (0x1D47C, 'M', 'u'), + (0x1D47D, 'M', 'v'), + (0x1D47E, 'M', 'w'), + (0x1D47F, 'M', 'x'), + (0x1D480, 'M', 'y'), + (0x1D481, 'M', 'z'), + (0x1D482, 'M', 'a'), + (0x1D483, 'M', 'b'), + (0x1D484, 'M', 'c'), + ] + +def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D485, 'M', 'd'), + (0x1D486, 'M', 'e'), + (0x1D487, 'M', 'f'), + (0x1D488, 'M', 'g'), + (0x1D489, 'M', 'h'), + (0x1D48A, 'M', 'i'), + (0x1D48B, 'M', 'j'), + (0x1D48C, 'M', 'k'), + (0x1D48D, 'M', 'l'), + (0x1D48E, 'M', 'm'), + (0x1D48F, 'M', 'n'), + (0x1D490, 'M', 'o'), + (0x1D491, 'M', 'p'), + (0x1D492, 'M', 'q'), + (0x1D493, 'M', 'r'), + (0x1D494, 'M', 's'), + (0x1D495, 'M', 't'), + (0x1D496, 'M', 'u'), + (0x1D497, 'M', 'v'), + (0x1D498, 'M', 'w'), + (0x1D499, 'M', 'x'), + (0x1D49A, 'M', 'y'), + (0x1D49B, 'M', 'z'), + (0x1D49C, 'M', 'a'), + (0x1D49D, 'X'), + (0x1D49E, 'M', 'c'), + (0x1D49F, 'M', 'd'), + (0x1D4A0, 'X'), + (0x1D4A2, 'M', 'g'), + (0x1D4A3, 'X'), + (0x1D4A5, 'M', 'j'), + (0x1D4A6, 'M', 'k'), + (0x1D4A7, 'X'), + (0x1D4A9, 'M', 'n'), + (0x1D4AA, 'M', 'o'), + (0x1D4AB, 'M', 'p'), + (0x1D4AC, 'M', 'q'), + (0x1D4AD, 'X'), + (0x1D4AE, 'M', 's'), + (0x1D4AF, 'M', 't'), + (0x1D4B0, 'M', 'u'), + (0x1D4B1, 'M', 'v'), + (0x1D4B2, 'M', 'w'), + (0x1D4B3, 'M', 'x'), + (0x1D4B4, 'M', 'y'), + (0x1D4B5, 'M', 'z'), + (0x1D4B6, 'M', 'a'), + (0x1D4B7, 'M', 'b'), + (0x1D4B8, 'M', 'c'), + (0x1D4B9, 'M', 'd'), + (0x1D4BA, 'X'), + (0x1D4BB, 'M', 'f'), + (0x1D4BC, 'X'), + (0x1D4BD, 'M', 'h'), + (0x1D4BE, 'M', 'i'), + (0x1D4BF, 'M', 'j'), + (0x1D4C0, 'M', 'k'), + (0x1D4C1, 'M', 'l'), + (0x1D4C2, 'M', 'm'), + (0x1D4C3, 'M', 'n'), + (0x1D4C4, 'X'), + (0x1D4C5, 'M', 'p'), + (0x1D4C6, 'M', 'q'), + (0x1D4C7, 'M', 'r'), + (0x1D4C8, 'M', 's'), + (0x1D4C9, 'M', 't'), + (0x1D4CA, 'M', 'u'), + (0x1D4CB, 'M', 'v'), + (0x1D4CC, 'M', 'w'), + (0x1D4CD, 'M', 'x'), + (0x1D4CE, 'M', 'y'), + (0x1D4CF, 'M', 'z'), + (0x1D4D0, 'M', 'a'), + (0x1D4D1, 'M', 'b'), + (0x1D4D2, 'M', 'c'), + (0x1D4D3, 'M', 'd'), + (0x1D4D4, 'M', 'e'), + (0x1D4D5, 'M', 'f'), + (0x1D4D6, 'M', 'g'), + (0x1D4D7, 'M', 'h'), + (0x1D4D8, 'M', 'i'), + (0x1D4D9, 'M', 'j'), + (0x1D4DA, 'M', 'k'), + (0x1D4DB, 'M', 'l'), + (0x1D4DC, 'M', 'm'), + (0x1D4DD, 'M', 'n'), + (0x1D4DE, 'M', 'o'), + (0x1D4DF, 'M', 'p'), + (0x1D4E0, 'M', 'q'), + (0x1D4E1, 'M', 'r'), + (0x1D4E2, 'M', 's'), + (0x1D4E3, 'M', 't'), + (0x1D4E4, 'M', 'u'), + (0x1D4E5, 'M', 'v'), + (0x1D4E6, 'M', 'w'), + (0x1D4E7, 'M', 'x'), + (0x1D4E8, 'M', 'y'), + (0x1D4E9, 'M', 'z'), + (0x1D4EA, 'M', 'a'), + (0x1D4EB, 'M', 'b'), + ] + +def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D4EC, 'M', 'c'), + (0x1D4ED, 'M', 'd'), + (0x1D4EE, 'M', 'e'), + (0x1D4EF, 'M', 'f'), + (0x1D4F0, 'M', 'g'), + (0x1D4F1, 'M', 'h'), + (0x1D4F2, 'M', 'i'), + (0x1D4F3, 'M', 'j'), + (0x1D4F4, 'M', 'k'), + (0x1D4F5, 'M', 'l'), + (0x1D4F6, 'M', 'm'), + (0x1D4F7, 'M', 'n'), + (0x1D4F8, 'M', 'o'), + (0x1D4F9, 'M', 'p'), + (0x1D4FA, 'M', 'q'), + (0x1D4FB, 'M', 'r'), + (0x1D4FC, 'M', 's'), + (0x1D4FD, 'M', 't'), + (0x1D4FE, 'M', 'u'), + (0x1D4FF, 'M', 'v'), + (0x1D500, 'M', 'w'), + (0x1D501, 'M', 'x'), + (0x1D502, 'M', 'y'), + (0x1D503, 'M', 'z'), + (0x1D504, 'M', 'a'), + (0x1D505, 'M', 'b'), + (0x1D506, 'X'), + (0x1D507, 'M', 'd'), + (0x1D508, 'M', 'e'), + (0x1D509, 'M', 'f'), + (0x1D50A, 'M', 'g'), + (0x1D50B, 'X'), + (0x1D50D, 'M', 'j'), + (0x1D50E, 'M', 'k'), + (0x1D50F, 'M', 'l'), + (0x1D510, 'M', 'm'), + (0x1D511, 'M', 'n'), + (0x1D512, 'M', 'o'), + (0x1D513, 'M', 'p'), + (0x1D514, 'M', 'q'), + (0x1D515, 'X'), + (0x1D516, 'M', 's'), + (0x1D517, 'M', 't'), + (0x1D518, 'M', 'u'), + (0x1D519, 'M', 'v'), + (0x1D51A, 'M', 'w'), + (0x1D51B, 'M', 'x'), + (0x1D51C, 'M', 'y'), + (0x1D51D, 'X'), + (0x1D51E, 'M', 'a'), + (0x1D51F, 'M', 'b'), + (0x1D520, 'M', 'c'), + (0x1D521, 'M', 'd'), + (0x1D522, 'M', 'e'), + (0x1D523, 'M', 'f'), + (0x1D524, 'M', 'g'), + (0x1D525, 'M', 'h'), + (0x1D526, 'M', 'i'), + (0x1D527, 'M', 'j'), + (0x1D528, 'M', 'k'), + (0x1D529, 'M', 'l'), + (0x1D52A, 'M', 'm'), + (0x1D52B, 'M', 'n'), + (0x1D52C, 'M', 'o'), + (0x1D52D, 'M', 'p'), + (0x1D52E, 'M', 'q'), + (0x1D52F, 'M', 'r'), + (0x1D530, 'M', 's'), + (0x1D531, 'M', 't'), + (0x1D532, 'M', 'u'), + (0x1D533, 'M', 'v'), + (0x1D534, 'M', 'w'), + (0x1D535, 'M', 'x'), + (0x1D536, 'M', 'y'), + (0x1D537, 'M', 'z'), + (0x1D538, 'M', 'a'), + (0x1D539, 'M', 'b'), + (0x1D53A, 'X'), + (0x1D53B, 'M', 'd'), + (0x1D53C, 'M', 'e'), + (0x1D53D, 'M', 'f'), + (0x1D53E, 'M', 'g'), + (0x1D53F, 'X'), + (0x1D540, 'M', 'i'), + (0x1D541, 'M', 'j'), + (0x1D542, 'M', 'k'), + (0x1D543, 'M', 'l'), + (0x1D544, 'M', 'm'), + (0x1D545, 'X'), + (0x1D546, 'M', 'o'), + (0x1D547, 'X'), + (0x1D54A, 'M', 's'), + (0x1D54B, 'M', 't'), + (0x1D54C, 'M', 'u'), + (0x1D54D, 'M', 'v'), + (0x1D54E, 'M', 'w'), + (0x1D54F, 'M', 'x'), + (0x1D550, 'M', 'y'), + (0x1D551, 'X'), + (0x1D552, 'M', 'a'), + ] + +def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D553, 'M', 'b'), + (0x1D554, 'M', 'c'), + (0x1D555, 'M', 'd'), + (0x1D556, 'M', 'e'), + (0x1D557, 'M', 'f'), + (0x1D558, 'M', 'g'), + (0x1D559, 'M', 'h'), + (0x1D55A, 'M', 'i'), + (0x1D55B, 'M', 'j'), + (0x1D55C, 'M', 'k'), + (0x1D55D, 'M', 'l'), + (0x1D55E, 'M', 'm'), + (0x1D55F, 'M', 'n'), + (0x1D560, 'M', 'o'), + (0x1D561, 'M', 'p'), + (0x1D562, 'M', 'q'), + (0x1D563, 'M', 'r'), + (0x1D564, 'M', 's'), + (0x1D565, 'M', 't'), + (0x1D566, 'M', 'u'), + (0x1D567, 'M', 'v'), + (0x1D568, 'M', 'w'), + (0x1D569, 'M', 'x'), + (0x1D56A, 'M', 'y'), + (0x1D56B, 'M', 'z'), + (0x1D56C, 'M', 'a'), + (0x1D56D, 'M', 'b'), + (0x1D56E, 'M', 'c'), + (0x1D56F, 'M', 'd'), + (0x1D570, 'M', 'e'), + (0x1D571, 'M', 'f'), + (0x1D572, 'M', 'g'), + (0x1D573, 'M', 'h'), + (0x1D574, 'M', 'i'), + (0x1D575, 'M', 'j'), + (0x1D576, 'M', 'k'), + (0x1D577, 'M', 'l'), + (0x1D578, 'M', 'm'), + (0x1D579, 'M', 'n'), + (0x1D57A, 'M', 'o'), + (0x1D57B, 'M', 'p'), + (0x1D57C, 'M', 'q'), + (0x1D57D, 'M', 'r'), + (0x1D57E, 'M', 's'), + (0x1D57F, 'M', 't'), + (0x1D580, 'M', 'u'), + (0x1D581, 'M', 'v'), + (0x1D582, 'M', 'w'), + (0x1D583, 'M', 'x'), + (0x1D584, 'M', 'y'), + (0x1D585, 'M', 'z'), + (0x1D586, 'M', 'a'), + (0x1D587, 'M', 'b'), + (0x1D588, 'M', 'c'), + (0x1D589, 'M', 'd'), + (0x1D58A, 'M', 'e'), + (0x1D58B, 'M', 'f'), + (0x1D58C, 'M', 'g'), + (0x1D58D, 'M', 'h'), + (0x1D58E, 'M', 'i'), + (0x1D58F, 'M', 'j'), + (0x1D590, 'M', 'k'), + (0x1D591, 'M', 'l'), + (0x1D592, 'M', 'm'), + (0x1D593, 'M', 'n'), + (0x1D594, 'M', 'o'), + (0x1D595, 'M', 'p'), + (0x1D596, 'M', 'q'), + (0x1D597, 'M', 'r'), + (0x1D598, 'M', 's'), + (0x1D599, 'M', 't'), + (0x1D59A, 'M', 'u'), + (0x1D59B, 'M', 'v'), + (0x1D59C, 'M', 'w'), + (0x1D59D, 'M', 'x'), + (0x1D59E, 'M', 'y'), + (0x1D59F, 'M', 'z'), + (0x1D5A0, 'M', 'a'), + (0x1D5A1, 'M', 'b'), + (0x1D5A2, 'M', 'c'), + (0x1D5A3, 'M', 'd'), + (0x1D5A4, 'M', 'e'), + (0x1D5A5, 'M', 'f'), + (0x1D5A6, 'M', 'g'), + (0x1D5A7, 'M', 'h'), + (0x1D5A8, 'M', 'i'), + (0x1D5A9, 'M', 'j'), + (0x1D5AA, 'M', 'k'), + (0x1D5AB, 'M', 'l'), + (0x1D5AC, 'M', 'm'), + (0x1D5AD, 'M', 'n'), + (0x1D5AE, 'M', 'o'), + (0x1D5AF, 'M', 'p'), + (0x1D5B0, 'M', 'q'), + (0x1D5B1, 'M', 'r'), + (0x1D5B2, 'M', 's'), + (0x1D5B3, 'M', 't'), + (0x1D5B4, 'M', 'u'), + (0x1D5B5, 'M', 'v'), + (0x1D5B6, 'M', 'w'), + ] + +def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D5B7, 'M', 'x'), + (0x1D5B8, 'M', 'y'), + (0x1D5B9, 'M', 'z'), + (0x1D5BA, 'M', 'a'), + (0x1D5BB, 'M', 'b'), + (0x1D5BC, 'M', 'c'), + (0x1D5BD, 'M', 'd'), + (0x1D5BE, 'M', 'e'), + (0x1D5BF, 'M', 'f'), + (0x1D5C0, 'M', 'g'), + (0x1D5C1, 'M', 'h'), + (0x1D5C2, 'M', 'i'), + (0x1D5C3, 'M', 'j'), + (0x1D5C4, 'M', 'k'), + (0x1D5C5, 'M', 'l'), + (0x1D5C6, 'M', 'm'), + (0x1D5C7, 'M', 'n'), + (0x1D5C8, 'M', 'o'), + (0x1D5C9, 'M', 'p'), + (0x1D5CA, 'M', 'q'), + (0x1D5CB, 'M', 'r'), + (0x1D5CC, 'M', 's'), + (0x1D5CD, 'M', 't'), + (0x1D5CE, 'M', 'u'), + (0x1D5CF, 'M', 'v'), + (0x1D5D0, 'M', 'w'), + (0x1D5D1, 'M', 'x'), + (0x1D5D2, 'M', 'y'), + (0x1D5D3, 'M', 'z'), + (0x1D5D4, 'M', 'a'), + (0x1D5D5, 'M', 'b'), + (0x1D5D6, 'M', 'c'), + (0x1D5D7, 'M', 'd'), + (0x1D5D8, 'M', 'e'), + (0x1D5D9, 'M', 'f'), + (0x1D5DA, 'M', 'g'), + (0x1D5DB, 'M', 'h'), + (0x1D5DC, 'M', 'i'), + (0x1D5DD, 'M', 'j'), + (0x1D5DE, 'M', 'k'), + (0x1D5DF, 'M', 'l'), + (0x1D5E0, 'M', 'm'), + (0x1D5E1, 'M', 'n'), + (0x1D5E2, 'M', 'o'), + (0x1D5E3, 'M', 'p'), + (0x1D5E4, 'M', 'q'), + (0x1D5E5, 'M', 'r'), + (0x1D5E6, 'M', 's'), + (0x1D5E7, 'M', 't'), + (0x1D5E8, 'M', 'u'), + (0x1D5E9, 'M', 'v'), + (0x1D5EA, 'M', 'w'), + (0x1D5EB, 'M', 'x'), + (0x1D5EC, 'M', 'y'), + (0x1D5ED, 'M', 'z'), + (0x1D5EE, 'M', 'a'), + (0x1D5EF, 'M', 'b'), + (0x1D5F0, 'M', 'c'), + (0x1D5F1, 'M', 'd'), + (0x1D5F2, 'M', 'e'), + (0x1D5F3, 'M', 'f'), + (0x1D5F4, 'M', 'g'), + (0x1D5F5, 'M', 'h'), + (0x1D5F6, 'M', 'i'), + (0x1D5F7, 'M', 'j'), + (0x1D5F8, 'M', 'k'), + (0x1D5F9, 'M', 'l'), + (0x1D5FA, 'M', 'm'), + (0x1D5FB, 'M', 'n'), + (0x1D5FC, 'M', 'o'), + (0x1D5FD, 'M', 'p'), + (0x1D5FE, 'M', 'q'), + (0x1D5FF, 'M', 'r'), + (0x1D600, 'M', 's'), + (0x1D601, 'M', 't'), + (0x1D602, 'M', 'u'), + (0x1D603, 'M', 'v'), + (0x1D604, 'M', 'w'), + (0x1D605, 'M', 'x'), + (0x1D606, 'M', 'y'), + (0x1D607, 'M', 'z'), + (0x1D608, 'M', 'a'), + (0x1D609, 'M', 'b'), + (0x1D60A, 'M', 'c'), + (0x1D60B, 'M', 'd'), + (0x1D60C, 'M', 'e'), + (0x1D60D, 'M', 'f'), + (0x1D60E, 'M', 'g'), + (0x1D60F, 'M', 'h'), + (0x1D610, 'M', 'i'), + (0x1D611, 'M', 'j'), + (0x1D612, 'M', 'k'), + (0x1D613, 'M', 'l'), + (0x1D614, 'M', 'm'), + (0x1D615, 'M', 'n'), + (0x1D616, 'M', 'o'), + (0x1D617, 'M', 'p'), + (0x1D618, 'M', 'q'), + (0x1D619, 'M', 'r'), + (0x1D61A, 'M', 's'), + ] + +def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D61B, 'M', 't'), + (0x1D61C, 'M', 'u'), + (0x1D61D, 'M', 'v'), + (0x1D61E, 'M', 'w'), + (0x1D61F, 'M', 'x'), + (0x1D620, 'M', 'y'), + (0x1D621, 'M', 'z'), + (0x1D622, 'M', 'a'), + (0x1D623, 'M', 'b'), + (0x1D624, 'M', 'c'), + (0x1D625, 'M', 'd'), + (0x1D626, 'M', 'e'), + (0x1D627, 'M', 'f'), + (0x1D628, 'M', 'g'), + (0x1D629, 'M', 'h'), + (0x1D62A, 'M', 'i'), + (0x1D62B, 'M', 'j'), + (0x1D62C, 'M', 'k'), + (0x1D62D, 'M', 'l'), + (0x1D62E, 'M', 'm'), + (0x1D62F, 'M', 'n'), + (0x1D630, 'M', 'o'), + (0x1D631, 'M', 'p'), + (0x1D632, 'M', 'q'), + (0x1D633, 'M', 'r'), + (0x1D634, 'M', 's'), + (0x1D635, 'M', 't'), + (0x1D636, 'M', 'u'), + (0x1D637, 'M', 'v'), + (0x1D638, 'M', 'w'), + (0x1D639, 'M', 'x'), + (0x1D63A, 'M', 'y'), + (0x1D63B, 'M', 'z'), + (0x1D63C, 'M', 'a'), + (0x1D63D, 'M', 'b'), + (0x1D63E, 'M', 'c'), + (0x1D63F, 'M', 'd'), + (0x1D640, 'M', 'e'), + (0x1D641, 'M', 'f'), + (0x1D642, 'M', 'g'), + (0x1D643, 'M', 'h'), + (0x1D644, 'M', 'i'), + (0x1D645, 'M', 'j'), + (0x1D646, 'M', 'k'), + (0x1D647, 'M', 'l'), + (0x1D648, 'M', 'm'), + (0x1D649, 'M', 'n'), + (0x1D64A, 'M', 'o'), + (0x1D64B, 'M', 'p'), + (0x1D64C, 'M', 'q'), + (0x1D64D, 'M', 'r'), + (0x1D64E, 'M', 's'), + (0x1D64F, 'M', 't'), + (0x1D650, 'M', 'u'), + (0x1D651, 'M', 'v'), + (0x1D652, 'M', 'w'), + (0x1D653, 'M', 'x'), + (0x1D654, 'M', 'y'), + (0x1D655, 'M', 'z'), + (0x1D656, 'M', 'a'), + (0x1D657, 'M', 'b'), + (0x1D658, 'M', 'c'), + (0x1D659, 'M', 'd'), + (0x1D65A, 'M', 'e'), + (0x1D65B, 'M', 'f'), + (0x1D65C, 'M', 'g'), + (0x1D65D, 'M', 'h'), + (0x1D65E, 'M', 'i'), + (0x1D65F, 'M', 'j'), + (0x1D660, 'M', 'k'), + (0x1D661, 'M', 'l'), + (0x1D662, 'M', 'm'), + (0x1D663, 'M', 'n'), + (0x1D664, 'M', 'o'), + (0x1D665, 'M', 'p'), + (0x1D666, 'M', 'q'), + (0x1D667, 'M', 'r'), + (0x1D668, 'M', 's'), + (0x1D669, 'M', 't'), + (0x1D66A, 'M', 'u'), + (0x1D66B, 'M', 'v'), + (0x1D66C, 'M', 'w'), + (0x1D66D, 'M', 'x'), + (0x1D66E, 'M', 'y'), + (0x1D66F, 'M', 'z'), + (0x1D670, 'M', 'a'), + (0x1D671, 'M', 'b'), + (0x1D672, 'M', 'c'), + (0x1D673, 'M', 'd'), + (0x1D674, 'M', 'e'), + (0x1D675, 'M', 'f'), + (0x1D676, 'M', 'g'), + (0x1D677, 'M', 'h'), + (0x1D678, 'M', 'i'), + (0x1D679, 'M', 'j'), + (0x1D67A, 'M', 'k'), + (0x1D67B, 'M', 'l'), + (0x1D67C, 'M', 'm'), + (0x1D67D, 'M', 'n'), + (0x1D67E, 'M', 'o'), + ] + +def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D67F, 'M', 'p'), + (0x1D680, 'M', 'q'), + (0x1D681, 'M', 'r'), + (0x1D682, 'M', 's'), + (0x1D683, 'M', 't'), + (0x1D684, 'M', 'u'), + (0x1D685, 'M', 'v'), + (0x1D686, 'M', 'w'), + (0x1D687, 'M', 'x'), + (0x1D688, 'M', 'y'), + (0x1D689, 'M', 'z'), + (0x1D68A, 'M', 'a'), + (0x1D68B, 'M', 'b'), + (0x1D68C, 'M', 'c'), + (0x1D68D, 'M', 'd'), + (0x1D68E, 'M', 'e'), + (0x1D68F, 'M', 'f'), + (0x1D690, 'M', 'g'), + (0x1D691, 'M', 'h'), + (0x1D692, 'M', 'i'), + (0x1D693, 'M', 'j'), + (0x1D694, 'M', 'k'), + (0x1D695, 'M', 'l'), + (0x1D696, 'M', 'm'), + (0x1D697, 'M', 'n'), + (0x1D698, 'M', 'o'), + (0x1D699, 'M', 'p'), + (0x1D69A, 'M', 'q'), + (0x1D69B, 'M', 'r'), + (0x1D69C, 'M', 's'), + (0x1D69D, 'M', 't'), + (0x1D69E, 'M', 'u'), + (0x1D69F, 'M', 'v'), + (0x1D6A0, 'M', 'w'), + (0x1D6A1, 'M', 'x'), + (0x1D6A2, 'M', 'y'), + (0x1D6A3, 'M', 'z'), + (0x1D6A4, 'M', 'ı'), + (0x1D6A5, 'M', 'ȷ'), + (0x1D6A6, 'X'), + (0x1D6A8, 'M', 'α'), + (0x1D6A9, 'M', 'β'), + (0x1D6AA, 'M', 'γ'), + (0x1D6AB, 'M', 'δ'), + (0x1D6AC, 'M', 'ε'), + (0x1D6AD, 'M', 'ζ'), + (0x1D6AE, 'M', 'η'), + (0x1D6AF, 'M', 'θ'), + (0x1D6B0, 'M', 'ι'), + (0x1D6B1, 'M', 'κ'), + (0x1D6B2, 'M', 'λ'), + (0x1D6B3, 'M', 'μ'), + (0x1D6B4, 'M', 'ν'), + (0x1D6B5, 'M', 'ξ'), + (0x1D6B6, 'M', 'ο'), + (0x1D6B7, 'M', 'π'), + (0x1D6B8, 'M', 'ρ'), + (0x1D6B9, 'M', 'θ'), + (0x1D6BA, 'M', 'σ'), + (0x1D6BB, 'M', 'τ'), + (0x1D6BC, 'M', 'υ'), + (0x1D6BD, 'M', 'φ'), + (0x1D6BE, 'M', 'χ'), + (0x1D6BF, 'M', 'ψ'), + (0x1D6C0, 'M', 'ω'), + (0x1D6C1, 'M', '∇'), + (0x1D6C2, 'M', 'α'), + (0x1D6C3, 'M', 'β'), + (0x1D6C4, 'M', 'γ'), + (0x1D6C5, 'M', 'δ'), + (0x1D6C6, 'M', 'ε'), + (0x1D6C7, 'M', 'ζ'), + (0x1D6C8, 'M', 'η'), + (0x1D6C9, 'M', 'θ'), + (0x1D6CA, 'M', 'ι'), + (0x1D6CB, 'M', 'κ'), + (0x1D6CC, 'M', 'λ'), + (0x1D6CD, 'M', 'μ'), + (0x1D6CE, 'M', 'ν'), + (0x1D6CF, 'M', 'ξ'), + (0x1D6D0, 'M', 'ο'), + (0x1D6D1, 'M', 'π'), + (0x1D6D2, 'M', 'ρ'), + (0x1D6D3, 'M', 'σ'), + (0x1D6D5, 'M', 'τ'), + (0x1D6D6, 'M', 'υ'), + (0x1D6D7, 'M', 'φ'), + (0x1D6D8, 'M', 'χ'), + (0x1D6D9, 'M', 'ψ'), + (0x1D6DA, 'M', 'ω'), + (0x1D6DB, 'M', '∂'), + (0x1D6DC, 'M', 'ε'), + (0x1D6DD, 'M', 'θ'), + (0x1D6DE, 'M', 'κ'), + (0x1D6DF, 'M', 'φ'), + (0x1D6E0, 'M', 'ρ'), + (0x1D6E1, 'M', 'π'), + (0x1D6E2, 'M', 'α'), + (0x1D6E3, 'M', 'β'), + (0x1D6E4, 'M', 'γ'), + ] + +def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D6E5, 'M', 'δ'), + (0x1D6E6, 'M', 'ε'), + (0x1D6E7, 'M', 'ζ'), + (0x1D6E8, 'M', 'η'), + (0x1D6E9, 'M', 'θ'), + (0x1D6EA, 'M', 'ι'), + (0x1D6EB, 'M', 'κ'), + (0x1D6EC, 'M', 'λ'), + (0x1D6ED, 'M', 'μ'), + (0x1D6EE, 'M', 'ν'), + (0x1D6EF, 'M', 'ξ'), + (0x1D6F0, 'M', 'ο'), + (0x1D6F1, 'M', 'π'), + (0x1D6F2, 'M', 'ρ'), + (0x1D6F3, 'M', 'θ'), + (0x1D6F4, 'M', 'σ'), + (0x1D6F5, 'M', 'τ'), + (0x1D6F6, 'M', 'υ'), + (0x1D6F7, 'M', 'φ'), + (0x1D6F8, 'M', 'χ'), + (0x1D6F9, 'M', 'ψ'), + (0x1D6FA, 'M', 'ω'), + (0x1D6FB, 'M', '∇'), + (0x1D6FC, 'M', 'α'), + (0x1D6FD, 'M', 'β'), + (0x1D6FE, 'M', 'γ'), + (0x1D6FF, 'M', 'δ'), + (0x1D700, 'M', 'ε'), + (0x1D701, 'M', 'ζ'), + (0x1D702, 'M', 'η'), + (0x1D703, 'M', 'θ'), + (0x1D704, 'M', 'ι'), + (0x1D705, 'M', 'κ'), + (0x1D706, 'M', 'λ'), + (0x1D707, 'M', 'μ'), + (0x1D708, 'M', 'ν'), + (0x1D709, 'M', 'ξ'), + (0x1D70A, 'M', 'ο'), + (0x1D70B, 'M', 'π'), + (0x1D70C, 'M', 'ρ'), + (0x1D70D, 'M', 'σ'), + (0x1D70F, 'M', 'τ'), + (0x1D710, 'M', 'υ'), + (0x1D711, 'M', 'φ'), + (0x1D712, 'M', 'χ'), + (0x1D713, 'M', 'ψ'), + (0x1D714, 'M', 'ω'), + (0x1D715, 'M', '∂'), + (0x1D716, 'M', 'ε'), + (0x1D717, 'M', 'θ'), + (0x1D718, 'M', 'κ'), + (0x1D719, 'M', 'φ'), + (0x1D71A, 'M', 'ρ'), + (0x1D71B, 'M', 'π'), + (0x1D71C, 'M', 'α'), + (0x1D71D, 'M', 'β'), + (0x1D71E, 'M', 'γ'), + (0x1D71F, 'M', 'δ'), + (0x1D720, 'M', 'ε'), + (0x1D721, 'M', 'ζ'), + (0x1D722, 'M', 'η'), + (0x1D723, 'M', 'θ'), + (0x1D724, 'M', 'ι'), + (0x1D725, 'M', 'κ'), + (0x1D726, 'M', 'λ'), + (0x1D727, 'M', 'μ'), + (0x1D728, 'M', 'ν'), + (0x1D729, 'M', 'ξ'), + (0x1D72A, 'M', 'ο'), + (0x1D72B, 'M', 'π'), + (0x1D72C, 'M', 'ρ'), + (0x1D72D, 'M', 'θ'), + (0x1D72E, 'M', 'σ'), + (0x1D72F, 'M', 'τ'), + (0x1D730, 'M', 'υ'), + (0x1D731, 'M', 'φ'), + (0x1D732, 'M', 'χ'), + (0x1D733, 'M', 'ψ'), + (0x1D734, 'M', 'ω'), + (0x1D735, 'M', '∇'), + (0x1D736, 'M', 'α'), + (0x1D737, 'M', 'β'), + (0x1D738, 'M', 'γ'), + (0x1D739, 'M', 'δ'), + (0x1D73A, 'M', 'ε'), + (0x1D73B, 'M', 'ζ'), + (0x1D73C, 'M', 'η'), + (0x1D73D, 'M', 'θ'), + (0x1D73E, 'M', 'ι'), + (0x1D73F, 'M', 'κ'), + (0x1D740, 'M', 'λ'), + (0x1D741, 'M', 'μ'), + (0x1D742, 'M', 'ν'), + (0x1D743, 'M', 'ξ'), + (0x1D744, 'M', 'ο'), + (0x1D745, 'M', 'π'), + (0x1D746, 'M', 'ρ'), + (0x1D747, 'M', 'σ'), + (0x1D749, 'M', 'τ'), + (0x1D74A, 'M', 'υ'), + ] + +def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D74B, 'M', 'φ'), + (0x1D74C, 'M', 'χ'), + (0x1D74D, 'M', 'ψ'), + (0x1D74E, 'M', 'ω'), + (0x1D74F, 'M', '∂'), + (0x1D750, 'M', 'ε'), + (0x1D751, 'M', 'θ'), + (0x1D752, 'M', 'κ'), + (0x1D753, 'M', 'φ'), + (0x1D754, 'M', 'ρ'), + (0x1D755, 'M', 'π'), + (0x1D756, 'M', 'α'), + (0x1D757, 'M', 'β'), + (0x1D758, 'M', 'γ'), + (0x1D759, 'M', 'δ'), + (0x1D75A, 'M', 'ε'), + (0x1D75B, 'M', 'ζ'), + (0x1D75C, 'M', 'η'), + (0x1D75D, 'M', 'θ'), + (0x1D75E, 'M', 'ι'), + (0x1D75F, 'M', 'κ'), + (0x1D760, 'M', 'λ'), + (0x1D761, 'M', 'μ'), + (0x1D762, 'M', 'ν'), + (0x1D763, 'M', 'ξ'), + (0x1D764, 'M', 'ο'), + (0x1D765, 'M', 'π'), + (0x1D766, 'M', 'ρ'), + (0x1D767, 'M', 'θ'), + (0x1D768, 'M', 'σ'), + (0x1D769, 'M', 'τ'), + (0x1D76A, 'M', 'υ'), + (0x1D76B, 'M', 'φ'), + (0x1D76C, 'M', 'χ'), + (0x1D76D, 'M', 'ψ'), + (0x1D76E, 'M', 'ω'), + (0x1D76F, 'M', '∇'), + (0x1D770, 'M', 'α'), + (0x1D771, 'M', 'β'), + (0x1D772, 'M', 'γ'), + (0x1D773, 'M', 'δ'), + (0x1D774, 'M', 'ε'), + (0x1D775, 'M', 'ζ'), + (0x1D776, 'M', 'η'), + (0x1D777, 'M', 'θ'), + (0x1D778, 'M', 'ι'), + (0x1D779, 'M', 'κ'), + (0x1D77A, 'M', 'λ'), + (0x1D77B, 'M', 'μ'), + (0x1D77C, 'M', 'ν'), + (0x1D77D, 'M', 'ξ'), + (0x1D77E, 'M', 'ο'), + (0x1D77F, 'M', 'π'), + (0x1D780, 'M', 'ρ'), + (0x1D781, 'M', 'σ'), + (0x1D783, 'M', 'τ'), + (0x1D784, 'M', 'υ'), + (0x1D785, 'M', 'φ'), + (0x1D786, 'M', 'χ'), + (0x1D787, 'M', 'ψ'), + (0x1D788, 'M', 'ω'), + (0x1D789, 'M', '∂'), + (0x1D78A, 'M', 'ε'), + (0x1D78B, 'M', 'θ'), + (0x1D78C, 'M', 'κ'), + (0x1D78D, 'M', 'φ'), + (0x1D78E, 'M', 'ρ'), + (0x1D78F, 'M', 'π'), + (0x1D790, 'M', 'α'), + (0x1D791, 'M', 'β'), + (0x1D792, 'M', 'γ'), + (0x1D793, 'M', 'δ'), + (0x1D794, 'M', 'ε'), + (0x1D795, 'M', 'ζ'), + (0x1D796, 'M', 'η'), + (0x1D797, 'M', 'θ'), + (0x1D798, 'M', 'ι'), + (0x1D799, 'M', 'κ'), + (0x1D79A, 'M', 'λ'), + (0x1D79B, 'M', 'μ'), + (0x1D79C, 'M', 'ν'), + (0x1D79D, 'M', 'ξ'), + (0x1D79E, 'M', 'ο'), + (0x1D79F, 'M', 'π'), + (0x1D7A0, 'M', 'ρ'), + (0x1D7A1, 'M', 'θ'), + (0x1D7A2, 'M', 'σ'), + (0x1D7A3, 'M', 'τ'), + (0x1D7A4, 'M', 'υ'), + (0x1D7A5, 'M', 'φ'), + (0x1D7A6, 'M', 'χ'), + (0x1D7A7, 'M', 'ψ'), + (0x1D7A8, 'M', 'ω'), + (0x1D7A9, 'M', '∇'), + (0x1D7AA, 'M', 'α'), + (0x1D7AB, 'M', 'β'), + (0x1D7AC, 'M', 'γ'), + (0x1D7AD, 'M', 'δ'), + (0x1D7AE, 'M', 'ε'), + (0x1D7AF, 'M', 'ζ'), + ] + +def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D7B0, 'M', 'η'), + (0x1D7B1, 'M', 'θ'), + (0x1D7B2, 'M', 'ι'), + (0x1D7B3, 'M', 'κ'), + (0x1D7B4, 'M', 'λ'), + (0x1D7B5, 'M', 'μ'), + (0x1D7B6, 'M', 'ν'), + (0x1D7B7, 'M', 'ξ'), + (0x1D7B8, 'M', 'ο'), + (0x1D7B9, 'M', 'π'), + (0x1D7BA, 'M', 'ρ'), + (0x1D7BB, 'M', 'σ'), + (0x1D7BD, 'M', 'τ'), + (0x1D7BE, 'M', 'υ'), + (0x1D7BF, 'M', 'φ'), + (0x1D7C0, 'M', 'χ'), + (0x1D7C1, 'M', 'ψ'), + (0x1D7C2, 'M', 'ω'), + (0x1D7C3, 'M', '∂'), + (0x1D7C4, 'M', 'ε'), + (0x1D7C5, 'M', 'θ'), + (0x1D7C6, 'M', 'κ'), + (0x1D7C7, 'M', 'φ'), + (0x1D7C8, 'M', 'ρ'), + (0x1D7C9, 'M', 'π'), + (0x1D7CA, 'M', 'ϝ'), + (0x1D7CC, 'X'), + (0x1D7CE, 'M', '0'), + (0x1D7CF, 'M', '1'), + (0x1D7D0, 'M', '2'), + (0x1D7D1, 'M', '3'), + (0x1D7D2, 'M', '4'), + (0x1D7D3, 'M', '5'), + (0x1D7D4, 'M', '6'), + (0x1D7D5, 'M', '7'), + (0x1D7D6, 'M', '8'), + (0x1D7D7, 'M', '9'), + (0x1D7D8, 'M', '0'), + (0x1D7D9, 'M', '1'), + (0x1D7DA, 'M', '2'), + (0x1D7DB, 'M', '3'), + (0x1D7DC, 'M', '4'), + (0x1D7DD, 'M', '5'), + (0x1D7DE, 'M', '6'), + (0x1D7DF, 'M', '7'), + (0x1D7E0, 'M', '8'), + (0x1D7E1, 'M', '9'), + (0x1D7E2, 'M', '0'), + (0x1D7E3, 'M', '1'), + (0x1D7E4, 'M', '2'), + (0x1D7E5, 'M', '3'), + (0x1D7E6, 'M', '4'), + (0x1D7E7, 'M', '5'), + (0x1D7E8, 'M', '6'), + (0x1D7E9, 'M', '7'), + (0x1D7EA, 'M', '8'), + (0x1D7EB, 'M', '9'), + (0x1D7EC, 'M', '0'), + (0x1D7ED, 'M', '1'), + (0x1D7EE, 'M', '2'), + (0x1D7EF, 'M', '3'), + (0x1D7F0, 'M', '4'), + (0x1D7F1, 'M', '5'), + (0x1D7F2, 'M', '6'), + (0x1D7F3, 'M', '7'), + (0x1D7F4, 'M', '8'), + (0x1D7F5, 'M', '9'), + (0x1D7F6, 'M', '0'), + (0x1D7F7, 'M', '1'), + (0x1D7F8, 'M', '2'), + (0x1D7F9, 'M', '3'), + (0x1D7FA, 'M', '4'), + (0x1D7FB, 'M', '5'), + (0x1D7FC, 'M', '6'), + (0x1D7FD, 'M', '7'), + (0x1D7FE, 'M', '8'), + (0x1D7FF, 'M', '9'), + (0x1D800, 'V'), + (0x1DA8C, 'X'), + (0x1DA9B, 'V'), + (0x1DAA0, 'X'), + (0x1DAA1, 'V'), + (0x1DAB0, 'X'), + (0x1DF00, 'V'), + (0x1DF1F, 'X'), + (0x1DF25, 'V'), + (0x1DF2B, 'X'), + (0x1E000, 'V'), + (0x1E007, 'X'), + (0x1E008, 'V'), + (0x1E019, 'X'), + (0x1E01B, 'V'), + (0x1E022, 'X'), + (0x1E023, 'V'), + (0x1E025, 'X'), + (0x1E026, 'V'), + (0x1E02B, 'X'), + (0x1E030, 'M', 'а'), + (0x1E031, 'M', 'б'), + (0x1E032, 'M', 'в'), + ] + +def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E033, 'M', 'г'), + (0x1E034, 'M', 'д'), + (0x1E035, 'M', 'е'), + (0x1E036, 'M', 'ж'), + (0x1E037, 'M', 'з'), + (0x1E038, 'M', 'и'), + (0x1E039, 'M', 'к'), + (0x1E03A, 'M', 'л'), + (0x1E03B, 'M', 'м'), + (0x1E03C, 'M', 'о'), + (0x1E03D, 'M', 'п'), + (0x1E03E, 'M', 'р'), + (0x1E03F, 'M', 'с'), + (0x1E040, 'M', 'т'), + (0x1E041, 'M', 'у'), + (0x1E042, 'M', 'ф'), + (0x1E043, 'M', 'х'), + (0x1E044, 'M', 'ц'), + (0x1E045, 'M', 'ч'), + (0x1E046, 'M', 'ш'), + (0x1E047, 'M', 'ы'), + (0x1E048, 'M', 'э'), + (0x1E049, 'M', 'ю'), + (0x1E04A, 'M', 'ꚉ'), + (0x1E04B, 'M', 'ә'), + (0x1E04C, 'M', 'і'), + (0x1E04D, 'M', 'ј'), + (0x1E04E, 'M', 'ө'), + (0x1E04F, 'M', 'ү'), + (0x1E050, 'M', 'ӏ'), + (0x1E051, 'M', 'а'), + (0x1E052, 'M', 'б'), + (0x1E053, 'M', 'в'), + (0x1E054, 'M', 'г'), + (0x1E055, 'M', 'д'), + (0x1E056, 'M', 'е'), + (0x1E057, 'M', 'ж'), + (0x1E058, 'M', 'з'), + (0x1E059, 'M', 'и'), + (0x1E05A, 'M', 'к'), + (0x1E05B, 'M', 'л'), + (0x1E05C, 'M', 'о'), + (0x1E05D, 'M', 'п'), + (0x1E05E, 'M', 'с'), + (0x1E05F, 'M', 'у'), + (0x1E060, 'M', 'ф'), + (0x1E061, 'M', 'х'), + (0x1E062, 'M', 'ц'), + (0x1E063, 'M', 'ч'), + (0x1E064, 'M', 'ш'), + (0x1E065, 'M', 'ъ'), + (0x1E066, 'M', 'ы'), + (0x1E067, 'M', 'ґ'), + (0x1E068, 'M', 'і'), + (0x1E069, 'M', 'ѕ'), + (0x1E06A, 'M', 'џ'), + (0x1E06B, 'M', 'ҫ'), + (0x1E06C, 'M', 'ꙑ'), + (0x1E06D, 'M', 'ұ'), + (0x1E06E, 'X'), + (0x1E08F, 'V'), + (0x1E090, 'X'), + (0x1E100, 'V'), + (0x1E12D, 'X'), + (0x1E130, 'V'), + (0x1E13E, 'X'), + (0x1E140, 'V'), + (0x1E14A, 'X'), + (0x1E14E, 'V'), + (0x1E150, 'X'), + (0x1E290, 'V'), + (0x1E2AF, 'X'), + (0x1E2C0, 'V'), + (0x1E2FA, 'X'), + (0x1E2FF, 'V'), + (0x1E300, 'X'), + (0x1E4D0, 'V'), + (0x1E4FA, 'X'), + (0x1E7E0, 'V'), + (0x1E7E7, 'X'), + (0x1E7E8, 'V'), + (0x1E7EC, 'X'), + (0x1E7ED, 'V'), + (0x1E7EF, 'X'), + (0x1E7F0, 'V'), + (0x1E7FF, 'X'), + (0x1E800, 'V'), + (0x1E8C5, 'X'), + (0x1E8C7, 'V'), + (0x1E8D7, 'X'), + (0x1E900, 'M', '𞤢'), + (0x1E901, 'M', '𞤣'), + (0x1E902, 'M', '𞤤'), + (0x1E903, 'M', '𞤥'), + (0x1E904, 'M', '𞤦'), + (0x1E905, 'M', '𞤧'), + (0x1E906, 'M', '𞤨'), + (0x1E907, 'M', '𞤩'), + (0x1E908, 'M', '𞤪'), + (0x1E909, 'M', '𞤫'), + ] + +def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E90A, 'M', '𞤬'), + (0x1E90B, 'M', '𞤭'), + (0x1E90C, 'M', '𞤮'), + (0x1E90D, 'M', '𞤯'), + (0x1E90E, 'M', '𞤰'), + (0x1E90F, 'M', '𞤱'), + (0x1E910, 'M', '𞤲'), + (0x1E911, 'M', '𞤳'), + (0x1E912, 'M', '𞤴'), + (0x1E913, 'M', '𞤵'), + (0x1E914, 'M', '𞤶'), + (0x1E915, 'M', '𞤷'), + (0x1E916, 'M', '𞤸'), + (0x1E917, 'M', '𞤹'), + (0x1E918, 'M', '𞤺'), + (0x1E919, 'M', '𞤻'), + (0x1E91A, 'M', '𞤼'), + (0x1E91B, 'M', '𞤽'), + (0x1E91C, 'M', '𞤾'), + (0x1E91D, 'M', '𞤿'), + (0x1E91E, 'M', '𞥀'), + (0x1E91F, 'M', '𞥁'), + (0x1E920, 'M', '𞥂'), + (0x1E921, 'M', '𞥃'), + (0x1E922, 'V'), + (0x1E94C, 'X'), + (0x1E950, 'V'), + (0x1E95A, 'X'), + (0x1E95E, 'V'), + (0x1E960, 'X'), + (0x1EC71, 'V'), + (0x1ECB5, 'X'), + (0x1ED01, 'V'), + (0x1ED3E, 'X'), + (0x1EE00, 'M', 'ا'), + (0x1EE01, 'M', 'ب'), + (0x1EE02, 'M', 'ج'), + (0x1EE03, 'M', 'د'), + (0x1EE04, 'X'), + (0x1EE05, 'M', 'و'), + (0x1EE06, 'M', 'ز'), + (0x1EE07, 'M', 'ح'), + (0x1EE08, 'M', 'ط'), + (0x1EE09, 'M', 'ي'), + (0x1EE0A, 'M', 'ك'), + (0x1EE0B, 'M', 'ل'), + (0x1EE0C, 'M', 'م'), + (0x1EE0D, 'M', 'ن'), + (0x1EE0E, 'M', 'س'), + (0x1EE0F, 'M', 'ع'), + (0x1EE10, 'M', 'ف'), + (0x1EE11, 'M', 'ص'), + (0x1EE12, 'M', 'ق'), + (0x1EE13, 'M', 'ر'), + (0x1EE14, 'M', 'ش'), + (0x1EE15, 'M', 'ت'), + (0x1EE16, 'M', 'ث'), + (0x1EE17, 'M', 'خ'), + (0x1EE18, 'M', 'ذ'), + (0x1EE19, 'M', 'ض'), + (0x1EE1A, 'M', 'ظ'), + (0x1EE1B, 'M', 'غ'), + (0x1EE1C, 'M', 'ٮ'), + (0x1EE1D, 'M', 'ں'), + (0x1EE1E, 'M', 'ڡ'), + (0x1EE1F, 'M', 'ٯ'), + (0x1EE20, 'X'), + (0x1EE21, 'M', 'ب'), + (0x1EE22, 'M', 'ج'), + (0x1EE23, 'X'), + (0x1EE24, 'M', 'ه'), + (0x1EE25, 'X'), + (0x1EE27, 'M', 'ح'), + (0x1EE28, 'X'), + (0x1EE29, 'M', 'ي'), + (0x1EE2A, 'M', 'ك'), + (0x1EE2B, 'M', 'ل'), + (0x1EE2C, 'M', 'م'), + (0x1EE2D, 'M', 'ن'), + (0x1EE2E, 'M', 'س'), + (0x1EE2F, 'M', 'ع'), + (0x1EE30, 'M', 'ف'), + (0x1EE31, 'M', 'ص'), + (0x1EE32, 'M', 'ق'), + (0x1EE33, 'X'), + (0x1EE34, 'M', 'ش'), + (0x1EE35, 'M', 'ت'), + (0x1EE36, 'M', 'ث'), + (0x1EE37, 'M', 'خ'), + (0x1EE38, 'X'), + (0x1EE39, 'M', 'ض'), + (0x1EE3A, 'X'), + (0x1EE3B, 'M', 'غ'), + (0x1EE3C, 'X'), + (0x1EE42, 'M', 'ج'), + (0x1EE43, 'X'), + (0x1EE47, 'M', 'ح'), + (0x1EE48, 'X'), + (0x1EE49, 'M', 'ي'), + (0x1EE4A, 'X'), + ] + +def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EE4B, 'M', 'ل'), + (0x1EE4C, 'X'), + (0x1EE4D, 'M', 'ن'), + (0x1EE4E, 'M', 'س'), + (0x1EE4F, 'M', 'ع'), + (0x1EE50, 'X'), + (0x1EE51, 'M', 'ص'), + (0x1EE52, 'M', 'ق'), + (0x1EE53, 'X'), + (0x1EE54, 'M', 'ش'), + (0x1EE55, 'X'), + (0x1EE57, 'M', 'خ'), + (0x1EE58, 'X'), + (0x1EE59, 'M', 'ض'), + (0x1EE5A, 'X'), + (0x1EE5B, 'M', 'غ'), + (0x1EE5C, 'X'), + (0x1EE5D, 'M', 'ں'), + (0x1EE5E, 'X'), + (0x1EE5F, 'M', 'ٯ'), + (0x1EE60, 'X'), + (0x1EE61, 'M', 'ب'), + (0x1EE62, 'M', 'ج'), + (0x1EE63, 'X'), + (0x1EE64, 'M', 'ه'), + (0x1EE65, 'X'), + (0x1EE67, 'M', 'ح'), + (0x1EE68, 'M', 'ط'), + (0x1EE69, 'M', 'ي'), + (0x1EE6A, 'M', 'ك'), + (0x1EE6B, 'X'), + (0x1EE6C, 'M', 'م'), + (0x1EE6D, 'M', 'ن'), + (0x1EE6E, 'M', 'س'), + (0x1EE6F, 'M', 'ع'), + (0x1EE70, 'M', 'ف'), + (0x1EE71, 'M', 'ص'), + (0x1EE72, 'M', 'ق'), + (0x1EE73, 'X'), + (0x1EE74, 'M', 'ش'), + (0x1EE75, 'M', 'ت'), + (0x1EE76, 'M', 'ث'), + (0x1EE77, 'M', 'خ'), + (0x1EE78, 'X'), + (0x1EE79, 'M', 'ض'), + (0x1EE7A, 'M', 'ظ'), + (0x1EE7B, 'M', 'غ'), + (0x1EE7C, 'M', 'ٮ'), + (0x1EE7D, 'X'), + (0x1EE7E, 'M', 'ڡ'), + (0x1EE7F, 'X'), + (0x1EE80, 'M', 'ا'), + (0x1EE81, 'M', 'ب'), + (0x1EE82, 'M', 'ج'), + (0x1EE83, 'M', 'د'), + (0x1EE84, 'M', 'ه'), + (0x1EE85, 'M', 'و'), + (0x1EE86, 'M', 'ز'), + (0x1EE87, 'M', 'ح'), + (0x1EE88, 'M', 'ط'), + (0x1EE89, 'M', 'ي'), + (0x1EE8A, 'X'), + (0x1EE8B, 'M', 'ل'), + (0x1EE8C, 'M', 'م'), + (0x1EE8D, 'M', 'ن'), + (0x1EE8E, 'M', 'س'), + (0x1EE8F, 'M', 'ع'), + (0x1EE90, 'M', 'ف'), + (0x1EE91, 'M', 'ص'), + (0x1EE92, 'M', 'ق'), + (0x1EE93, 'M', 'ر'), + (0x1EE94, 'M', 'ش'), + (0x1EE95, 'M', 'ت'), + (0x1EE96, 'M', 'ث'), + (0x1EE97, 'M', 'خ'), + (0x1EE98, 'M', 'ذ'), + (0x1EE99, 'M', 'ض'), + (0x1EE9A, 'M', 'ظ'), + (0x1EE9B, 'M', 'غ'), + (0x1EE9C, 'X'), + (0x1EEA1, 'M', 'ب'), + (0x1EEA2, 'M', 'ج'), + (0x1EEA3, 'M', 'د'), + (0x1EEA4, 'X'), + (0x1EEA5, 'M', 'و'), + (0x1EEA6, 'M', 'ز'), + (0x1EEA7, 'M', 'ح'), + (0x1EEA8, 'M', 'ط'), + (0x1EEA9, 'M', 'ي'), + (0x1EEAA, 'X'), + (0x1EEAB, 'M', 'ل'), + (0x1EEAC, 'M', 'م'), + (0x1EEAD, 'M', 'ن'), + (0x1EEAE, 'M', 'س'), + (0x1EEAF, 'M', 'ع'), + (0x1EEB0, 'M', 'ف'), + (0x1EEB1, 'M', 'ص'), + (0x1EEB2, 'M', 'ق'), + (0x1EEB3, 'M', 'ر'), + (0x1EEB4, 'M', 'ش'), + ] + +def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EEB5, 'M', 'ت'), + (0x1EEB6, 'M', 'ث'), + (0x1EEB7, 'M', 'خ'), + (0x1EEB8, 'M', 'ذ'), + (0x1EEB9, 'M', 'ض'), + (0x1EEBA, 'M', 'ظ'), + (0x1EEBB, 'M', 'غ'), + (0x1EEBC, 'X'), + (0x1EEF0, 'V'), + (0x1EEF2, 'X'), + (0x1F000, 'V'), + (0x1F02C, 'X'), + (0x1F030, 'V'), + (0x1F094, 'X'), + (0x1F0A0, 'V'), + (0x1F0AF, 'X'), + (0x1F0B1, 'V'), + (0x1F0C0, 'X'), + (0x1F0C1, 'V'), + (0x1F0D0, 'X'), + (0x1F0D1, 'V'), + (0x1F0F6, 'X'), + (0x1F101, '3', '0,'), + (0x1F102, '3', '1,'), + (0x1F103, '3', '2,'), + (0x1F104, '3', '3,'), + (0x1F105, '3', '4,'), + (0x1F106, '3', '5,'), + (0x1F107, '3', '6,'), + (0x1F108, '3', '7,'), + (0x1F109, '3', '8,'), + (0x1F10A, '3', '9,'), + (0x1F10B, 'V'), + (0x1F110, '3', '(a)'), + (0x1F111, '3', '(b)'), + (0x1F112, '3', '(c)'), + (0x1F113, '3', '(d)'), + (0x1F114, '3', '(e)'), + (0x1F115, '3', '(f)'), + (0x1F116, '3', '(g)'), + (0x1F117, '3', '(h)'), + (0x1F118, '3', '(i)'), + (0x1F119, '3', '(j)'), + (0x1F11A, '3', '(k)'), + (0x1F11B, '3', '(l)'), + (0x1F11C, '3', '(m)'), + (0x1F11D, '3', '(n)'), + (0x1F11E, '3', '(o)'), + (0x1F11F, '3', '(p)'), + (0x1F120, '3', '(q)'), + (0x1F121, '3', '(r)'), + (0x1F122, '3', '(s)'), + (0x1F123, '3', '(t)'), + (0x1F124, '3', '(u)'), + (0x1F125, '3', '(v)'), + (0x1F126, '3', '(w)'), + (0x1F127, '3', '(x)'), + (0x1F128, '3', '(y)'), + (0x1F129, '3', '(z)'), + (0x1F12A, 'M', '〔s〕'), + (0x1F12B, 'M', 'c'), + (0x1F12C, 'M', 'r'), + (0x1F12D, 'M', 'cd'), + (0x1F12E, 'M', 'wz'), + (0x1F12F, 'V'), + (0x1F130, 'M', 'a'), + (0x1F131, 'M', 'b'), + (0x1F132, 'M', 'c'), + (0x1F133, 'M', 'd'), + (0x1F134, 'M', 'e'), + (0x1F135, 'M', 'f'), + (0x1F136, 'M', 'g'), + (0x1F137, 'M', 'h'), + (0x1F138, 'M', 'i'), + (0x1F139, 'M', 'j'), + (0x1F13A, 'M', 'k'), + (0x1F13B, 'M', 'l'), + (0x1F13C, 'M', 'm'), + (0x1F13D, 'M', 'n'), + (0x1F13E, 'M', 'o'), + (0x1F13F, 'M', 'p'), + (0x1F140, 'M', 'q'), + (0x1F141, 'M', 'r'), + (0x1F142, 'M', 's'), + (0x1F143, 'M', 't'), + (0x1F144, 'M', 'u'), + (0x1F145, 'M', 'v'), + (0x1F146, 'M', 'w'), + (0x1F147, 'M', 'x'), + (0x1F148, 'M', 'y'), + (0x1F149, 'M', 'z'), + (0x1F14A, 'M', 'hv'), + (0x1F14B, 'M', 'mv'), + (0x1F14C, 'M', 'sd'), + (0x1F14D, 'M', 'ss'), + (0x1F14E, 'M', 'ppv'), + (0x1F14F, 'M', 'wc'), + (0x1F150, 'V'), + (0x1F16A, 'M', 'mc'), + (0x1F16B, 'M', 'md'), + ] + +def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1F16C, 'M', 'mr'), + (0x1F16D, 'V'), + (0x1F190, 'M', 'dj'), + (0x1F191, 'V'), + (0x1F1AE, 'X'), + (0x1F1E6, 'V'), + (0x1F200, 'M', 'ほか'), + (0x1F201, 'M', 'ココ'), + (0x1F202, 'M', 'サ'), + (0x1F203, 'X'), + (0x1F210, 'M', '手'), + (0x1F211, 'M', '字'), + (0x1F212, 'M', '双'), + (0x1F213, 'M', 'デ'), + (0x1F214, 'M', '二'), + (0x1F215, 'M', '多'), + (0x1F216, 'M', '解'), + (0x1F217, 'M', '天'), + (0x1F218, 'M', '交'), + (0x1F219, 'M', '映'), + (0x1F21A, 'M', '無'), + (0x1F21B, 'M', '料'), + (0x1F21C, 'M', '前'), + (0x1F21D, 'M', '後'), + (0x1F21E, 'M', '再'), + (0x1F21F, 'M', '新'), + (0x1F220, 'M', '初'), + (0x1F221, 'M', '終'), + (0x1F222, 'M', '生'), + (0x1F223, 'M', '販'), + (0x1F224, 'M', '声'), + (0x1F225, 'M', '吹'), + (0x1F226, 'M', '演'), + (0x1F227, 'M', '投'), + (0x1F228, 'M', '捕'), + (0x1F229, 'M', '一'), + (0x1F22A, 'M', '三'), + (0x1F22B, 'M', '遊'), + (0x1F22C, 'M', '左'), + (0x1F22D, 'M', '中'), + (0x1F22E, 'M', '右'), + (0x1F22F, 'M', '指'), + (0x1F230, 'M', '走'), + (0x1F231, 'M', '打'), + (0x1F232, 'M', '禁'), + (0x1F233, 'M', '空'), + (0x1F234, 'M', '合'), + (0x1F235, 'M', '満'), + (0x1F236, 'M', '有'), + (0x1F237, 'M', '月'), + (0x1F238, 'M', '申'), + (0x1F239, 'M', '割'), + (0x1F23A, 'M', '営'), + (0x1F23B, 'M', '配'), + (0x1F23C, 'X'), + (0x1F240, 'M', '〔本〕'), + (0x1F241, 'M', '〔三〕'), + (0x1F242, 'M', '〔二〕'), + (0x1F243, 'M', '〔安〕'), + (0x1F244, 'M', '〔点〕'), + (0x1F245, 'M', '〔打〕'), + (0x1F246, 'M', '〔盗〕'), + (0x1F247, 'M', '〔勝〕'), + (0x1F248, 'M', '〔敗〕'), + (0x1F249, 'X'), + (0x1F250, 'M', '得'), + (0x1F251, 'M', '可'), + (0x1F252, 'X'), + (0x1F260, 'V'), + (0x1F266, 'X'), + (0x1F300, 'V'), + (0x1F6D8, 'X'), + (0x1F6DC, 'V'), + (0x1F6ED, 'X'), + (0x1F6F0, 'V'), + (0x1F6FD, 'X'), + (0x1F700, 'V'), + (0x1F777, 'X'), + (0x1F77B, 'V'), + (0x1F7DA, 'X'), + (0x1F7E0, 'V'), + (0x1F7EC, 'X'), + (0x1F7F0, 'V'), + (0x1F7F1, 'X'), + (0x1F800, 'V'), + (0x1F80C, 'X'), + (0x1F810, 'V'), + (0x1F848, 'X'), + (0x1F850, 'V'), + (0x1F85A, 'X'), + (0x1F860, 'V'), + (0x1F888, 'X'), + (0x1F890, 'V'), + (0x1F8AE, 'X'), + (0x1F8B0, 'V'), + (0x1F8B2, 'X'), + (0x1F900, 'V'), + (0x1FA54, 'X'), + (0x1FA60, 'V'), + (0x1FA6E, 'X'), + ] + +def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1FA70, 'V'), + (0x1FA7D, 'X'), + (0x1FA80, 'V'), + (0x1FA89, 'X'), + (0x1FA90, 'V'), + (0x1FABE, 'X'), + (0x1FABF, 'V'), + (0x1FAC6, 'X'), + (0x1FACE, 'V'), + (0x1FADC, 'X'), + (0x1FAE0, 'V'), + (0x1FAE9, 'X'), + (0x1FAF0, 'V'), + (0x1FAF9, 'X'), + (0x1FB00, 'V'), + (0x1FB93, 'X'), + (0x1FB94, 'V'), + (0x1FBCB, 'X'), + (0x1FBF0, 'M', '0'), + (0x1FBF1, 'M', '1'), + (0x1FBF2, 'M', '2'), + (0x1FBF3, 'M', '3'), + (0x1FBF4, 'M', '4'), + (0x1FBF5, 'M', '5'), + (0x1FBF6, 'M', '6'), + (0x1FBF7, 'M', '7'), + (0x1FBF8, 'M', '8'), + (0x1FBF9, 'M', '9'), + (0x1FBFA, 'X'), + (0x20000, 'V'), + (0x2A6E0, 'X'), + (0x2A700, 'V'), + (0x2B73A, 'X'), + (0x2B740, 'V'), + (0x2B81E, 'X'), + (0x2B820, 'V'), + (0x2CEA2, 'X'), + (0x2CEB0, 'V'), + (0x2EBE1, 'X'), + (0x2F800, 'M', '丽'), + (0x2F801, 'M', '丸'), + (0x2F802, 'M', '乁'), + (0x2F803, 'M', '𠄢'), + (0x2F804, 'M', '你'), + (0x2F805, 'M', '侮'), + (0x2F806, 'M', '侻'), + (0x2F807, 'M', '倂'), + (0x2F808, 'M', '偺'), + (0x2F809, 'M', '備'), + (0x2F80A, 'M', '僧'), + (0x2F80B, 'M', '像'), + (0x2F80C, 'M', '㒞'), + (0x2F80D, 'M', '𠘺'), + (0x2F80E, 'M', '免'), + (0x2F80F, 'M', '兔'), + (0x2F810, 'M', '兤'), + (0x2F811, 'M', '具'), + (0x2F812, 'M', '𠔜'), + (0x2F813, 'M', '㒹'), + (0x2F814, 'M', '內'), + (0x2F815, 'M', '再'), + (0x2F816, 'M', '𠕋'), + (0x2F817, 'M', '冗'), + (0x2F818, 'M', '冤'), + (0x2F819, 'M', '仌'), + (0x2F81A, 'M', '冬'), + (0x2F81B, 'M', '况'), + (0x2F81C, 'M', '𩇟'), + (0x2F81D, 'M', '凵'), + (0x2F81E, 'M', '刃'), + (0x2F81F, 'M', '㓟'), + (0x2F820, 'M', '刻'), + (0x2F821, 'M', '剆'), + (0x2F822, 'M', '割'), + (0x2F823, 'M', '剷'), + (0x2F824, 'M', '㔕'), + (0x2F825, 'M', '勇'), + (0x2F826, 'M', '勉'), + (0x2F827, 'M', '勤'), + (0x2F828, 'M', '勺'), + (0x2F829, 'M', '包'), + (0x2F82A, 'M', '匆'), + (0x2F82B, 'M', '北'), + (0x2F82C, 'M', '卉'), + (0x2F82D, 'M', '卑'), + (0x2F82E, 'M', '博'), + (0x2F82F, 'M', '即'), + (0x2F830, 'M', '卽'), + (0x2F831, 'M', '卿'), + (0x2F834, 'M', '𠨬'), + (0x2F835, 'M', '灰'), + (0x2F836, 'M', '及'), + (0x2F837, 'M', '叟'), + (0x2F838, 'M', '𠭣'), + (0x2F839, 'M', '叫'), + (0x2F83A, 'M', '叱'), + (0x2F83B, 'M', '吆'), + (0x2F83C, 'M', '咞'), + (0x2F83D, 'M', '吸'), + (0x2F83E, 'M', '呈'), + ] + +def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F83F, 'M', '周'), + (0x2F840, 'M', '咢'), + (0x2F841, 'M', '哶'), + (0x2F842, 'M', '唐'), + (0x2F843, 'M', '啓'), + (0x2F844, 'M', '啣'), + (0x2F845, 'M', '善'), + (0x2F847, 'M', '喙'), + (0x2F848, 'M', '喫'), + (0x2F849, 'M', '喳'), + (0x2F84A, 'M', '嗂'), + (0x2F84B, 'M', '圖'), + (0x2F84C, 'M', '嘆'), + (0x2F84D, 'M', '圗'), + (0x2F84E, 'M', '噑'), + (0x2F84F, 'M', '噴'), + (0x2F850, 'M', '切'), + (0x2F851, 'M', '壮'), + (0x2F852, 'M', '城'), + (0x2F853, 'M', '埴'), + (0x2F854, 'M', '堍'), + (0x2F855, 'M', '型'), + (0x2F856, 'M', '堲'), + (0x2F857, 'M', '報'), + (0x2F858, 'M', '墬'), + (0x2F859, 'M', '𡓤'), + (0x2F85A, 'M', '売'), + (0x2F85B, 'M', '壷'), + (0x2F85C, 'M', '夆'), + (0x2F85D, 'M', '多'), + (0x2F85E, 'M', '夢'), + (0x2F85F, 'M', '奢'), + (0x2F860, 'M', '𡚨'), + (0x2F861, 'M', '𡛪'), + (0x2F862, 'M', '姬'), + (0x2F863, 'M', '娛'), + (0x2F864, 'M', '娧'), + (0x2F865, 'M', '姘'), + (0x2F866, 'M', '婦'), + (0x2F867, 'M', '㛮'), + (0x2F868, 'X'), + (0x2F869, 'M', '嬈'), + (0x2F86A, 'M', '嬾'), + (0x2F86C, 'M', '𡧈'), + (0x2F86D, 'M', '寃'), + (0x2F86E, 'M', '寘'), + (0x2F86F, 'M', '寧'), + (0x2F870, 'M', '寳'), + (0x2F871, 'M', '𡬘'), + (0x2F872, 'M', '寿'), + (0x2F873, 'M', '将'), + (0x2F874, 'X'), + (0x2F875, 'M', '尢'), + (0x2F876, 'M', '㞁'), + (0x2F877, 'M', '屠'), + (0x2F878, 'M', '屮'), + (0x2F879, 'M', '峀'), + (0x2F87A, 'M', '岍'), + (0x2F87B, 'M', '𡷤'), + (0x2F87C, 'M', '嵃'), + (0x2F87D, 'M', '𡷦'), + (0x2F87E, 'M', '嵮'), + (0x2F87F, 'M', '嵫'), + (0x2F880, 'M', '嵼'), + (0x2F881, 'M', '巡'), + (0x2F882, 'M', '巢'), + (0x2F883, 'M', '㠯'), + (0x2F884, 'M', '巽'), + (0x2F885, 'M', '帨'), + (0x2F886, 'M', '帽'), + (0x2F887, 'M', '幩'), + (0x2F888, 'M', '㡢'), + (0x2F889, 'M', '𢆃'), + (0x2F88A, 'M', '㡼'), + (0x2F88B, 'M', '庰'), + (0x2F88C, 'M', '庳'), + (0x2F88D, 'M', '庶'), + (0x2F88E, 'M', '廊'), + (0x2F88F, 'M', '𪎒'), + (0x2F890, 'M', '廾'), + (0x2F891, 'M', '𢌱'), + (0x2F893, 'M', '舁'), + (0x2F894, 'M', '弢'), + (0x2F896, 'M', '㣇'), + (0x2F897, 'M', '𣊸'), + (0x2F898, 'M', '𦇚'), + (0x2F899, 'M', '形'), + (0x2F89A, 'M', '彫'), + (0x2F89B, 'M', '㣣'), + (0x2F89C, 'M', '徚'), + (0x2F89D, 'M', '忍'), + (0x2F89E, 'M', '志'), + (0x2F89F, 'M', '忹'), + (0x2F8A0, 'M', '悁'), + (0x2F8A1, 'M', '㤺'), + (0x2F8A2, 'M', '㤜'), + (0x2F8A3, 'M', '悔'), + (0x2F8A4, 'M', '𢛔'), + (0x2F8A5, 'M', '惇'), + (0x2F8A6, 'M', '慈'), + ] + +def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F8A7, 'M', '慌'), + (0x2F8A8, 'M', '慎'), + (0x2F8A9, 'M', '慌'), + (0x2F8AA, 'M', '慺'), + (0x2F8AB, 'M', '憎'), + (0x2F8AC, 'M', '憲'), + (0x2F8AD, 'M', '憤'), + (0x2F8AE, 'M', '憯'), + (0x2F8AF, 'M', '懞'), + (0x2F8B0, 'M', '懲'), + (0x2F8B1, 'M', '懶'), + (0x2F8B2, 'M', '成'), + (0x2F8B3, 'M', '戛'), + (0x2F8B4, 'M', '扝'), + (0x2F8B5, 'M', '抱'), + (0x2F8B6, 'M', '拔'), + (0x2F8B7, 'M', '捐'), + (0x2F8B8, 'M', '𢬌'), + (0x2F8B9, 'M', '挽'), + (0x2F8BA, 'M', '拼'), + (0x2F8BB, 'M', '捨'), + (0x2F8BC, 'M', '掃'), + (0x2F8BD, 'M', '揤'), + (0x2F8BE, 'M', '𢯱'), + (0x2F8BF, 'M', '搢'), + (0x2F8C0, 'M', '揅'), + (0x2F8C1, 'M', '掩'), + (0x2F8C2, 'M', '㨮'), + (0x2F8C3, 'M', '摩'), + (0x2F8C4, 'M', '摾'), + (0x2F8C5, 'M', '撝'), + (0x2F8C6, 'M', '摷'), + (0x2F8C7, 'M', '㩬'), + (0x2F8C8, 'M', '敏'), + (0x2F8C9, 'M', '敬'), + (0x2F8CA, 'M', '𣀊'), + (0x2F8CB, 'M', '旣'), + (0x2F8CC, 'M', '書'), + (0x2F8CD, 'M', '晉'), + (0x2F8CE, 'M', '㬙'), + (0x2F8CF, 'M', '暑'), + (0x2F8D0, 'M', '㬈'), + (0x2F8D1, 'M', '㫤'), + (0x2F8D2, 'M', '冒'), + (0x2F8D3, 'M', '冕'), + (0x2F8D4, 'M', '最'), + (0x2F8D5, 'M', '暜'), + (0x2F8D6, 'M', '肭'), + (0x2F8D7, 'M', '䏙'), + (0x2F8D8, 'M', '朗'), + (0x2F8D9, 'M', '望'), + (0x2F8DA, 'M', '朡'), + (0x2F8DB, 'M', '杞'), + (0x2F8DC, 'M', '杓'), + (0x2F8DD, 'M', '𣏃'), + (0x2F8DE, 'M', '㭉'), + (0x2F8DF, 'M', '柺'), + (0x2F8E0, 'M', '枅'), + (0x2F8E1, 'M', '桒'), + (0x2F8E2, 'M', '梅'), + (0x2F8E3, 'M', '𣑭'), + (0x2F8E4, 'M', '梎'), + (0x2F8E5, 'M', '栟'), + (0x2F8E6, 'M', '椔'), + (0x2F8E7, 'M', '㮝'), + (0x2F8E8, 'M', '楂'), + (0x2F8E9, 'M', '榣'), + (0x2F8EA, 'M', '槪'), + (0x2F8EB, 'M', '檨'), + (0x2F8EC, 'M', '𣚣'), + (0x2F8ED, 'M', '櫛'), + (0x2F8EE, 'M', '㰘'), + (0x2F8EF, 'M', '次'), + (0x2F8F0, 'M', '𣢧'), + (0x2F8F1, 'M', '歔'), + (0x2F8F2, 'M', '㱎'), + (0x2F8F3, 'M', '歲'), + (0x2F8F4, 'M', '殟'), + (0x2F8F5, 'M', '殺'), + (0x2F8F6, 'M', '殻'), + (0x2F8F7, 'M', '𣪍'), + (0x2F8F8, 'M', '𡴋'), + (0x2F8F9, 'M', '𣫺'), + (0x2F8FA, 'M', '汎'), + (0x2F8FB, 'M', '𣲼'), + (0x2F8FC, 'M', '沿'), + (0x2F8FD, 'M', '泍'), + (0x2F8FE, 'M', '汧'), + (0x2F8FF, 'M', '洖'), + (0x2F900, 'M', '派'), + (0x2F901, 'M', '海'), + (0x2F902, 'M', '流'), + (0x2F903, 'M', '浩'), + (0x2F904, 'M', '浸'), + (0x2F905, 'M', '涅'), + (0x2F906, 'M', '𣴞'), + (0x2F907, 'M', '洴'), + (0x2F908, 'M', '港'), + (0x2F909, 'M', '湮'), + (0x2F90A, 'M', '㴳'), + ] + +def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F90B, 'M', '滋'), + (0x2F90C, 'M', '滇'), + (0x2F90D, 'M', '𣻑'), + (0x2F90E, 'M', '淹'), + (0x2F90F, 'M', '潮'), + (0x2F910, 'M', '𣽞'), + (0x2F911, 'M', '𣾎'), + (0x2F912, 'M', '濆'), + (0x2F913, 'M', '瀹'), + (0x2F914, 'M', '瀞'), + (0x2F915, 'M', '瀛'), + (0x2F916, 'M', '㶖'), + (0x2F917, 'M', '灊'), + (0x2F918, 'M', '災'), + (0x2F919, 'M', '灷'), + (0x2F91A, 'M', '炭'), + (0x2F91B, 'M', '𠔥'), + (0x2F91C, 'M', '煅'), + (0x2F91D, 'M', '𤉣'), + (0x2F91E, 'M', '熜'), + (0x2F91F, 'X'), + (0x2F920, 'M', '爨'), + (0x2F921, 'M', '爵'), + (0x2F922, 'M', '牐'), + (0x2F923, 'M', '𤘈'), + (0x2F924, 'M', '犀'), + (0x2F925, 'M', '犕'), + (0x2F926, 'M', '𤜵'), + (0x2F927, 'M', '𤠔'), + (0x2F928, 'M', '獺'), + (0x2F929, 'M', '王'), + (0x2F92A, 'M', '㺬'), + (0x2F92B, 'M', '玥'), + (0x2F92C, 'M', '㺸'), + (0x2F92E, 'M', '瑇'), + (0x2F92F, 'M', '瑜'), + (0x2F930, 'M', '瑱'), + (0x2F931, 'M', '璅'), + (0x2F932, 'M', '瓊'), + (0x2F933, 'M', '㼛'), + (0x2F934, 'M', '甤'), + (0x2F935, 'M', '𤰶'), + (0x2F936, 'M', '甾'), + (0x2F937, 'M', '𤲒'), + (0x2F938, 'M', '異'), + (0x2F939, 'M', '𢆟'), + (0x2F93A, 'M', '瘐'), + (0x2F93B, 'M', '𤾡'), + (0x2F93C, 'M', '𤾸'), + (0x2F93D, 'M', '𥁄'), + (0x2F93E, 'M', '㿼'), + (0x2F93F, 'M', '䀈'), + (0x2F940, 'M', '直'), + (0x2F941, 'M', '𥃳'), + (0x2F942, 'M', '𥃲'), + (0x2F943, 'M', '𥄙'), + (0x2F944, 'M', '𥄳'), + (0x2F945, 'M', '眞'), + (0x2F946, 'M', '真'), + (0x2F948, 'M', '睊'), + (0x2F949, 'M', '䀹'), + (0x2F94A, 'M', '瞋'), + (0x2F94B, 'M', '䁆'), + (0x2F94C, 'M', '䂖'), + (0x2F94D, 'M', '𥐝'), + (0x2F94E, 'M', '硎'), + (0x2F94F, 'M', '碌'), + (0x2F950, 'M', '磌'), + (0x2F951, 'M', '䃣'), + (0x2F952, 'M', '𥘦'), + (0x2F953, 'M', '祖'), + (0x2F954, 'M', '𥚚'), + (0x2F955, 'M', '𥛅'), + (0x2F956, 'M', '福'), + (0x2F957, 'M', '秫'), + (0x2F958, 'M', '䄯'), + (0x2F959, 'M', '穀'), + (0x2F95A, 'M', '穊'), + (0x2F95B, 'M', '穏'), + (0x2F95C, 'M', '𥥼'), + (0x2F95D, 'M', '𥪧'), + (0x2F95F, 'X'), + (0x2F960, 'M', '䈂'), + (0x2F961, 'M', '𥮫'), + (0x2F962, 'M', '篆'), + (0x2F963, 'M', '築'), + (0x2F964, 'M', '䈧'), + (0x2F965, 'M', '𥲀'), + (0x2F966, 'M', '糒'), + (0x2F967, 'M', '䊠'), + (0x2F968, 'M', '糨'), + (0x2F969, 'M', '糣'), + (0x2F96A, 'M', '紀'), + (0x2F96B, 'M', '𥾆'), + (0x2F96C, 'M', '絣'), + (0x2F96D, 'M', '䌁'), + (0x2F96E, 'M', '緇'), + (0x2F96F, 'M', '縂'), + (0x2F970, 'M', '繅'), + (0x2F971, 'M', '䌴'), + ] + +def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F972, 'M', '𦈨'), + (0x2F973, 'M', '𦉇'), + (0x2F974, 'M', '䍙'), + (0x2F975, 'M', '𦋙'), + (0x2F976, 'M', '罺'), + (0x2F977, 'M', '𦌾'), + (0x2F978, 'M', '羕'), + (0x2F979, 'M', '翺'), + (0x2F97A, 'M', '者'), + (0x2F97B, 'M', '𦓚'), + (0x2F97C, 'M', '𦔣'), + (0x2F97D, 'M', '聠'), + (0x2F97E, 'M', '𦖨'), + (0x2F97F, 'M', '聰'), + (0x2F980, 'M', '𣍟'), + (0x2F981, 'M', '䏕'), + (0x2F982, 'M', '育'), + (0x2F983, 'M', '脃'), + (0x2F984, 'M', '䐋'), + (0x2F985, 'M', '脾'), + (0x2F986, 'M', '媵'), + (0x2F987, 'M', '𦞧'), + (0x2F988, 'M', '𦞵'), + (0x2F989, 'M', '𣎓'), + (0x2F98A, 'M', '𣎜'), + (0x2F98B, 'M', '舁'), + (0x2F98C, 'M', '舄'), + (0x2F98D, 'M', '辞'), + (0x2F98E, 'M', '䑫'), + (0x2F98F, 'M', '芑'), + (0x2F990, 'M', '芋'), + (0x2F991, 'M', '芝'), + (0x2F992, 'M', '劳'), + (0x2F993, 'M', '花'), + (0x2F994, 'M', '芳'), + (0x2F995, 'M', '芽'), + (0x2F996, 'M', '苦'), + (0x2F997, 'M', '𦬼'), + (0x2F998, 'M', '若'), + (0x2F999, 'M', '茝'), + (0x2F99A, 'M', '荣'), + (0x2F99B, 'M', '莭'), + (0x2F99C, 'M', '茣'), + (0x2F99D, 'M', '莽'), + (0x2F99E, 'M', '菧'), + (0x2F99F, 'M', '著'), + (0x2F9A0, 'M', '荓'), + (0x2F9A1, 'M', '菊'), + (0x2F9A2, 'M', '菌'), + (0x2F9A3, 'M', '菜'), + (0x2F9A4, 'M', '𦰶'), + (0x2F9A5, 'M', '𦵫'), + (0x2F9A6, 'M', '𦳕'), + (0x2F9A7, 'M', '䔫'), + (0x2F9A8, 'M', '蓱'), + (0x2F9A9, 'M', '蓳'), + (0x2F9AA, 'M', '蔖'), + (0x2F9AB, 'M', '𧏊'), + (0x2F9AC, 'M', '蕤'), + (0x2F9AD, 'M', '𦼬'), + (0x2F9AE, 'M', '䕝'), + (0x2F9AF, 'M', '䕡'), + (0x2F9B0, 'M', '𦾱'), + (0x2F9B1, 'M', '𧃒'), + (0x2F9B2, 'M', '䕫'), + (0x2F9B3, 'M', '虐'), + (0x2F9B4, 'M', '虜'), + (0x2F9B5, 'M', '虧'), + (0x2F9B6, 'M', '虩'), + (0x2F9B7, 'M', '蚩'), + (0x2F9B8, 'M', '蚈'), + (0x2F9B9, 'M', '蜎'), + (0x2F9BA, 'M', '蛢'), + (0x2F9BB, 'M', '蝹'), + (0x2F9BC, 'M', '蜨'), + (0x2F9BD, 'M', '蝫'), + (0x2F9BE, 'M', '螆'), + (0x2F9BF, 'X'), + (0x2F9C0, 'M', '蟡'), + (0x2F9C1, 'M', '蠁'), + (0x2F9C2, 'M', '䗹'), + (0x2F9C3, 'M', '衠'), + (0x2F9C4, 'M', '衣'), + (0x2F9C5, 'M', '𧙧'), + (0x2F9C6, 'M', '裗'), + (0x2F9C7, 'M', '裞'), + (0x2F9C8, 'M', '䘵'), + (0x2F9C9, 'M', '裺'), + (0x2F9CA, 'M', '㒻'), + (0x2F9CB, 'M', '𧢮'), + (0x2F9CC, 'M', '𧥦'), + (0x2F9CD, 'M', '䚾'), + (0x2F9CE, 'M', '䛇'), + (0x2F9CF, 'M', '誠'), + (0x2F9D0, 'M', '諭'), + (0x2F9D1, 'M', '變'), + (0x2F9D2, 'M', '豕'), + (0x2F9D3, 'M', '𧲨'), + (0x2F9D4, 'M', '貫'), + (0x2F9D5, 'M', '賁'), + ] + +def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F9D6, 'M', '贛'), + (0x2F9D7, 'M', '起'), + (0x2F9D8, 'M', '𧼯'), + (0x2F9D9, 'M', '𠠄'), + (0x2F9DA, 'M', '跋'), + (0x2F9DB, 'M', '趼'), + (0x2F9DC, 'M', '跰'), + (0x2F9DD, 'M', '𠣞'), + (0x2F9DE, 'M', '軔'), + (0x2F9DF, 'M', '輸'), + (0x2F9E0, 'M', '𨗒'), + (0x2F9E1, 'M', '𨗭'), + (0x2F9E2, 'M', '邔'), + (0x2F9E3, 'M', '郱'), + (0x2F9E4, 'M', '鄑'), + (0x2F9E5, 'M', '𨜮'), + (0x2F9E6, 'M', '鄛'), + (0x2F9E7, 'M', '鈸'), + (0x2F9E8, 'M', '鋗'), + (0x2F9E9, 'M', '鋘'), + (0x2F9EA, 'M', '鉼'), + (0x2F9EB, 'M', '鏹'), + (0x2F9EC, 'M', '鐕'), + (0x2F9ED, 'M', '𨯺'), + (0x2F9EE, 'M', '開'), + (0x2F9EF, 'M', '䦕'), + (0x2F9F0, 'M', '閷'), + (0x2F9F1, 'M', '𨵷'), + (0x2F9F2, 'M', '䧦'), + (0x2F9F3, 'M', '雃'), + (0x2F9F4, 'M', '嶲'), + (0x2F9F5, 'M', '霣'), + (0x2F9F6, 'M', '𩅅'), + (0x2F9F7, 'M', '𩈚'), + (0x2F9F8, 'M', '䩮'), + (0x2F9F9, 'M', '䩶'), + (0x2F9FA, 'M', '韠'), + (0x2F9FB, 'M', '𩐊'), + (0x2F9FC, 'M', '䪲'), + (0x2F9FD, 'M', '𩒖'), + (0x2F9FE, 'M', '頋'), + (0x2FA00, 'M', '頩'), + (0x2FA01, 'M', '𩖶'), + (0x2FA02, 'M', '飢'), + (0x2FA03, 'M', '䬳'), + (0x2FA04, 'M', '餩'), + (0x2FA05, 'M', '馧'), + (0x2FA06, 'M', '駂'), + (0x2FA07, 'M', '駾'), + (0x2FA08, 'M', '䯎'), + (0x2FA09, 'M', '𩬰'), + (0x2FA0A, 'M', '鬒'), + (0x2FA0B, 'M', '鱀'), + (0x2FA0C, 'M', '鳽'), + (0x2FA0D, 'M', '䳎'), + (0x2FA0E, 'M', '䳭'), + (0x2FA0F, 'M', '鵧'), + (0x2FA10, 'M', '𪃎'), + (0x2FA11, 'M', '䳸'), + (0x2FA12, 'M', '𪄅'), + (0x2FA13, 'M', '𪈎'), + (0x2FA14, 'M', '𪊑'), + (0x2FA15, 'M', '麻'), + (0x2FA16, 'M', '䵖'), + (0x2FA17, 'M', '黹'), + (0x2FA18, 'M', '黾'), + (0x2FA19, 'M', '鼅'), + (0x2FA1A, 'M', '鼏'), + (0x2FA1B, 'M', '鼖'), + (0x2FA1C, 'M', '鼻'), + (0x2FA1D, 'M', '𪘀'), + (0x2FA1E, 'X'), + (0x30000, 'V'), + (0x3134B, 'X'), + (0x31350, 'V'), + (0x323B0, 'X'), + (0xE0100, 'I'), + (0xE01F0, 'X'), + ] + +uts46data = tuple( + _seg_0() + + _seg_1() + + _seg_2() + + _seg_3() + + _seg_4() + + _seg_5() + + _seg_6() + + _seg_7() + + _seg_8() + + _seg_9() + + _seg_10() + + _seg_11() + + _seg_12() + + _seg_13() + + _seg_14() + + _seg_15() + + _seg_16() + + _seg_17() + + _seg_18() + + _seg_19() + + _seg_20() + + _seg_21() + + _seg_22() + + _seg_23() + + _seg_24() + + _seg_25() + + _seg_26() + + _seg_27() + + _seg_28() + + _seg_29() + + _seg_30() + + _seg_31() + + _seg_32() + + _seg_33() + + _seg_34() + + _seg_35() + + _seg_36() + + _seg_37() + + _seg_38() + + _seg_39() + + _seg_40() + + _seg_41() + + _seg_42() + + _seg_43() + + _seg_44() + + _seg_45() + + _seg_46() + + _seg_47() + + _seg_48() + + _seg_49() + + _seg_50() + + _seg_51() + + _seg_52() + + _seg_53() + + _seg_54() + + _seg_55() + + _seg_56() + + _seg_57() + + _seg_58() + + _seg_59() + + _seg_60() + + _seg_61() + + _seg_62() + + _seg_63() + + _seg_64() + + _seg_65() + + _seg_66() + + _seg_67() + + _seg_68() + + _seg_69() + + _seg_70() + + _seg_71() + + _seg_72() + + _seg_73() + + _seg_74() + + _seg_75() + + _seg_76() + + _seg_77() + + _seg_78() + + _seg_79() + + _seg_80() + + _seg_81() +) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...] diff --git a/lib/multidict-6.0.4.dist-info/INSTALLER b/lib/multidict-6.0.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/multidict-6.0.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/multidict-6.0.4.dist-info/LICENSE b/lib/multidict-6.0.4.dist-info/LICENSE new file mode 100644 index 0000000..305eef6 --- /dev/null +++ b/lib/multidict-6.0.4.dist-info/LICENSE @@ -0,0 +1,13 @@ + Copyright 2016-2021 Andrew Svetlov and aio-libs team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/multidict-6.0.4.dist-info/METADATA b/lib/multidict-6.0.4.dist-info/METADATA new file mode 100644 index 0000000..55377fc --- /dev/null +++ b/lib/multidict-6.0.4.dist-info/METADATA @@ -0,0 +1,130 @@ +Metadata-Version: 2.1 +Name: multidict +Version: 6.0.4 +Summary: multidict implementation +Home-page: https://github.com/aio-libs/multidict +Author: Andrew Svetlov +Author-email: andrew.svetlov@gmail.com +License: Apache 2 +Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby +Project-URL: CI: GitHub, https://github.com/aio-libs/multidict/actions +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/multidict +Project-URL: Docs: RTD, https://multidict.readthedocs.io +Project-URL: GitHub: issues, https://github.com/aio-libs/multidict/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/multidict +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Development Status :: 5 - Production/Stable +Requires-Python: >=3.7 +License-File: LICENSE + +========= +multidict +========= + +.. image:: https://github.com/aio-libs/multidict/workflows/CI/badge.svg + :target: https://github.com/aio-libs/multidict/actions?query=workflow%3ACI + :alt: GitHub status for master branch + +.. image:: https://codecov.io/gh/aio-libs/multidict/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/multidict + :alt: Coverage metrics + +.. image:: https://img.shields.io/pypi/v/multidict.svg + :target: https://pypi.org/project/multidict + :alt: PyPI + +.. image:: https://readthedocs.org/projects/multidict/badge/?version=latest + :target: http://multidict.readthedocs.org/en/latest/?badge=latest + :alt: Documentationb + +.. image:: https://img.shields.io/pypi/pyversions/multidict.svg + :target: https://pypi.org/project/multidict + :alt: Python versions + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :target: https://gitter.im/aio-libs/Lobby + :alt: Chat on Gitter + +Multidict is dict-like collection of *key-value pairs* where key +might occur more than once in the container. + +Introduction +------------ + +*HTTP Headers* and *URL query string* require specific data structure: +*multidict*. It behaves mostly like a regular ``dict`` but it may have +several *values* for the same *key* and *preserves insertion ordering*. + +The *key* is ``str`` (or ``istr`` for case-insensitive dictionaries). + +``multidict`` has four multidict classes: +``MultiDict``, ``MultiDictProxy``, ``CIMultiDict`` +and ``CIMultiDictProxy``. + +Immutable proxies (``MultiDictProxy`` and +``CIMultiDictProxy``) provide a dynamic view for the +proxied multidict, the view reflects underlying collection changes. They +implement the ``collections.abc.Mapping`` interface. + +Regular mutable (``MultiDict`` and ``CIMultiDict``) classes +implement ``collections.abc.MutableMapping`` and allows them to change +their own content. + + +*Case insensitive* (``CIMultiDict`` and +``CIMultiDictProxy``) assume the *keys* are case +insensitive, e.g.:: + + >>> dct = CIMultiDict(key='val') + >>> 'Key' in dct + True + >>> dct['Key'] + 'val' + +*Keys* should be ``str`` or ``istr`` instances. + +The library has optional C Extensions for speed. + + +License +------- + +Apache 2 + +Library Installation +-------------------- + +.. code-block:: bash + + $ pip install multidict + +The library is Python 3 only! + +PyPI contains binary wheels for Linux, Windows and MacOS. If you want to install +``multidict`` on another operating system (or *Alpine Linux* inside a Docker) the +tarball will be used to compile the library from source. It requires a C compiler and +Python headers to be installed. + +To skip the compilation, please use the `MULTIDICT_NO_EXTENSIONS` environment variable, +e.g.: + +.. code-block:: bash + + $ MULTIDICT_NO_EXTENSIONS=1 pip install multidict + +Please note, the pure Python (uncompiled) version is about 20-50 times slower depending on +the usage scenario!!! + + + +Changelog +--------- +See `RTD page `_. diff --git a/lib/multidict-6.0.4.dist-info/RECORD b/lib/multidict-6.0.4.dist-info/RECORD new file mode 100644 index 0000000..d69773a --- /dev/null +++ b/lib/multidict-6.0.4.dist-info/RECORD @@ -0,0 +1,19 @@ +multidict-6.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +multidict-6.0.4.dist-info/LICENSE,sha256=OCAHB92pwCQzIBTmdKlWLv4aXShg88UqCtNSKlg5DUE,621 +multidict-6.0.4.dist-info/METADATA,sha256=io0JZdpWtcowypcF-O3HuzThY2qOoVd5JHim37W_lF8,4270 +multidict-6.0.4.dist-info/RECORD,, +multidict-6.0.4.dist-info/WHEEL,sha256=J_4V_gB-O6Y7Pn6lk91K27JaIhI-q07YM5J8Ufzqla4,100 +multidict-6.0.4.dist-info/top_level.txt,sha256=-euDElkk5_qkmfIJ7WiqCab02ZlSFZWynejKg59qZQQ,10 +multidict/__init__.py,sha256=mY4YGSMgwwOgcBqSuFcEYlJh72MmvPDilSf05L7wMik,976 +multidict/__init__.pyi,sha256=Snjf741LdwYBQk-3F5JcEpLQTjrFT6QgB2UcuARawyk,5003 +multidict/__pycache__/__init__.cpython-39.pyc,, +multidict/__pycache__/_abc.cpython-39.pyc,, +multidict/__pycache__/_compat.cpython-39.pyc,, +multidict/__pycache__/_multidict_base.cpython-39.pyc,, +multidict/__pycache__/_multidict_py.cpython-39.pyc,, +multidict/_abc.py,sha256=5a8deo_qMHWH6x-FmBYkljkLhWOJngwSnxQWFoKjn9E,1238 +multidict/_compat.py,sha256=cLjdGfIaDkrFYtxRVl2yP-dmoJZa-CMThQ7vtLwHsg0,330 +multidict/_multidict.cp39-win_amd64.pyd,sha256=JQEQ6SQ7QmdHKFObtC3K2nIpSjLbVZoG5yHX4KombtA,46592 +multidict/_multidict_base.py,sha256=YWQNBePG7WBk_JdwY9tt0HNzzMvouxKamGNa-WolXRE,3935 +multidict/_multidict_py.py,sha256=vUeEfppRMcrT7YPAg3AFML3vep_yPO9hXg_SkNGd8eU,15575 +multidict/py.typed,sha256=e9bmbH3UFxsabQrnNFPG9qxIXztwbcM6IKDYnvZwprY,15 diff --git a/lib/multidict-6.0.4.dist-info/WHEEL b/lib/multidict-6.0.4.dist-info/WHEEL new file mode 100644 index 0000000..d22c9ab --- /dev/null +++ b/lib/multidict-6.0.4.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: false +Tag: cp39-cp39-win_amd64 + diff --git a/lib/multidict-6.0.4.dist-info/top_level.txt b/lib/multidict-6.0.4.dist-info/top_level.txt new file mode 100644 index 0000000..afcecdf --- /dev/null +++ b/lib/multidict-6.0.4.dist-info/top_level.txt @@ -0,0 +1 @@ +multidict diff --git a/lib/multidict/__init__.py b/lib/multidict/__init__.py new file mode 100644 index 0000000..d9ea722 --- /dev/null +++ b/lib/multidict/__init__.py @@ -0,0 +1,48 @@ +"""Multidict implementation. + +HTTP Headers and URL query string require specific data structure: +multidict. It behaves mostly like a dict but it can have +several values for the same key. +""" + +from ._abc import MultiMapping, MutableMultiMapping +from ._compat import USE_EXTENSIONS + +__all__ = ( + "MultiMapping", + "MutableMultiMapping", + "MultiDictProxy", + "CIMultiDictProxy", + "MultiDict", + "CIMultiDict", + "upstr", + "istr", + "getversion", +) + +__version__ = "6.0.4" + + +try: + if not USE_EXTENSIONS: + raise ImportError + from ._multidict import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + getversion, + istr, + ) +except ImportError: # pragma: no cover + from ._multidict_py import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + getversion, + istr, + ) + + +upstr = istr diff --git a/lib/multidict/__init__.pyi b/lib/multidict/__init__.pyi new file mode 100644 index 0000000..bbc5a5c --- /dev/null +++ b/lib/multidict/__init__.pyi @@ -0,0 +1,150 @@ +import abc +from typing import ( + Generic, + Iterable, + Iterator, + Mapping, + MutableMapping, + TypeVar, + overload, +) + +class istr(str): ... + +upstr = istr + +_S = str | istr + +_T = TypeVar("_T") + +_T_co = TypeVar("_T_co", covariant=True) + +_D = TypeVar("_D") + +class MultiMapping(Mapping[_S, _T_co]): + @overload + @abc.abstractmethod + def getall(self, key: _S) -> list[_T_co]: ... + @overload + @abc.abstractmethod + def getall(self, key: _S, default: _D) -> list[_T_co] | _D: ... + @overload + @abc.abstractmethod + def getone(self, key: _S) -> _T_co: ... + @overload + @abc.abstractmethod + def getone(self, key: _S, default: _D) -> _T_co | _D: ... + +_Arg = (Mapping[str, _T] | Mapping[istr, _T] | dict[str, _T] + | dict[istr, _T] | MultiMapping[_T] + | Iterable[tuple[str, _T]] | Iterable[tuple[istr, _T]]) + +class MutableMultiMapping(MultiMapping[_T], MutableMapping[_S, _T], Generic[_T]): + @abc.abstractmethod + def add(self, key: _S, value: _T) -> None: ... + @abc.abstractmethod + def extend(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + @overload + @abc.abstractmethod + def popone(self, key: _S) -> _T: ... + @overload + @abc.abstractmethod + def popone(self, key: _S, default: _D) -> _T | _D: ... + @overload + @abc.abstractmethod + def popall(self, key: _S) -> list[_T]: ... + @overload + @abc.abstractmethod + def popall(self, key: _S, default: _D) -> list[_T] | _D: ... + +class MultiDict(MutableMultiMapping[_T], Generic[_T]): + def __init__(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + def copy(self) -> MultiDict[_T]: ... + def __getitem__(self, k: _S) -> _T: ... + def __setitem__(self, k: _S, v: _T) -> None: ... + def __delitem__(self, v: _S) -> None: ... + def __iter__(self) -> Iterator[_S]: ... + def __len__(self) -> int: ... + @overload + def getall(self, key: _S) -> list[_T]: ... + @overload + def getall(self, key: _S, default: _D) -> list[_T] | _D: ... + @overload + def getone(self, key: _S) -> _T: ... + @overload + def getone(self, key: _S, default: _D) -> _T | _D: ... + def add(self, key: _S, value: _T) -> None: ... + def extend(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + @overload + def popone(self, key: _S) -> _T: ... + @overload + def popone(self, key: _S, default: _D) -> _T | _D: ... + @overload + def popall(self, key: _S) -> list[_T]: ... + @overload + def popall(self, key: _S, default: _D) -> list[_T] | _D: ... + +class CIMultiDict(MutableMultiMapping[_T], Generic[_T]): + def __init__(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + def copy(self) -> CIMultiDict[_T]: ... + def __getitem__(self, k: _S) -> _T: ... + def __setitem__(self, k: _S, v: _T) -> None: ... + def __delitem__(self, v: _S) -> None: ... + def __iter__(self) -> Iterator[_S]: ... + def __len__(self) -> int: ... + @overload + def getall(self, key: _S) -> list[_T]: ... + @overload + def getall(self, key: _S, default: _D) -> list[_T] | _D: ... + @overload + def getone(self, key: _S) -> _T: ... + @overload + def getone(self, key: _S, default: _D) -> _T | _D: ... + def add(self, key: _S, value: _T) -> None: ... + def extend(self, arg: _Arg[_T] = ..., **kwargs: _T) -> None: ... + @overload + def popone(self, key: _S) -> _T: ... + @overload + def popone(self, key: _S, default: _D) -> _T | _D: ... + @overload + def popall(self, key: _S) -> list[_T]: ... + @overload + def popall(self, key: _S, default: _D) -> list[_T] | _D: ... + +class MultiDictProxy(MultiMapping[_T], Generic[_T]): + def __init__( + self, arg: MultiMapping[_T] | MutableMultiMapping[_T] + ) -> None: ... + def copy(self) -> MultiDict[_T]: ... + def __getitem__(self, k: _S) -> _T: ... + def __iter__(self) -> Iterator[_S]: ... + def __len__(self) -> int: ... + @overload + def getall(self, key: _S) -> list[_T]: ... + @overload + def getall(self, key: _S, default: _D) -> list[_T] | _D: ... + @overload + def getone(self, key: _S) -> _T: ... + @overload + def getone(self, key: _S, default: _D) -> _T | _D: ... + +class CIMultiDictProxy(MultiMapping[_T], Generic[_T]): + def __init__( + self, arg: MultiMapping[_T] | MutableMultiMapping[_T] + ) -> None: ... + def __getitem__(self, k: _S) -> _T: ... + def __iter__(self) -> Iterator[_S]: ... + def __len__(self) -> int: ... + @overload + def getall(self, key: _S) -> list[_T]: ... + @overload + def getall(self, key: _S, default: _D) -> list[_T] | _D: ... + @overload + def getone(self, key: _S) -> _T: ... + @overload + def getone(self, key: _S, default: _D) -> _T | _D: ... + def copy(self) -> CIMultiDict[_T]: ... + +def getversion( + md: MultiDict[_T] | CIMultiDict[_T] | MultiDictProxy[_T] | CIMultiDictProxy[_T] +) -> int: ... diff --git a/lib/multidict/__pycache__/__init__.cpython-39.pyc b/lib/multidict/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcee09ce04920231b6dc556bb54a88c27cfa04fe GIT binary patch literal 845 zcmaJZQK$q)&X=APvvi4H3wYgmxd0W*U;#>IsB;2ihM!>urjTj7TKAq$_)*C;Ox? z2V@{O$cBgBqHHHX(5^mt$jvo>^WXgUDD$@wY>=(AHATbr>f2E%3{QDtFqee@37KJr z=h-NV4$jU_@d2N zwf=ut(!XWWqEa0j;0sb)PL_jTCwsf`xwfgq(?szi-Uq!fc@fVGL-nQm4Y2`jcH%=m zi#0d!v|!1C%|XXE5}r}YGj1py6;<7SDf6@x@XEDJKd=zAHocx7^n!-(1$I7a_q@l^ EZ$M1%AOHXW literal 0 HcmV?d00001 diff --git a/lib/multidict/__pycache__/_abc.cpython-39.pyc b/lib/multidict/__pycache__/_abc.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf6b06ac4d29010ea8618cebc94c118b90499e76 GIT binary patch literal 1979 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 literal 0 HcmV?d00001 diff --git a/lib/multidict/__pycache__/_multidict_py.cpython-39.pyc b/lib/multidict/__pycache__/_multidict_py.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d0395ae3df6a4c57eed366509e025d0de7e1205 GIT binary patch literal 16881 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 (3, 9): + + def __getitem__(self, key): + return types.GenericAlias(self, key) + + else: + + def __getitem__(self, key): + return self + + +class MultiMapping(Mapping, metaclass=_TypingMeta): + @abc.abstractmethod + def getall(self, key, default=None): + raise KeyError + + @abc.abstractmethod + def getone(self, key, default=None): + raise KeyError + + +class MutableMultiMapping(MultiMapping, MutableMapping): + @abc.abstractmethod + def add(self, key, value): + raise NotImplementedError + + @abc.abstractmethod + def extend(self, *args, **kwargs): + raise NotImplementedError + + @abc.abstractmethod + def popone(self, key, default=None): + raise KeyError + + @abc.abstractmethod + def popall(self, key, default=None): + raise KeyError diff --git a/lib/multidict/_compat.py b/lib/multidict/_compat.py new file mode 100644 index 0000000..d1ff392 --- /dev/null +++ b/lib/multidict/_compat.py @@ -0,0 +1,14 @@ +import os +import platform + +NO_EXTENSIONS = bool(os.environ.get("MULTIDICT_NO_EXTENSIONS")) + +PYPY = platform.python_implementation() == "PyPy" + +USE_EXTENSIONS = not NO_EXTENSIONS and not PYPY + +if USE_EXTENSIONS: + try: + from . import _multidict # noqa + except ImportError: + USE_EXTENSIONS = False diff --git a/lib/multidict/_multidict.cp39-win_amd64.pyd b/lib/multidict/_multidict.cp39-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..237d702099a856a1f9d783d6d2cf3617eadb3eed GIT binary patch literal 46592 zcmeHw4R}<=)&B&tAq19N@QdTx zqg*eJ%PlFbRyCmib}7t!mU*KDwNU+C3C?drM$A(J;iE`OO&Lm{_4!V z=HZEhjPI=Xo*Xm{&$T@hVhG;(o`AJIm&9Dl@t4M2&tYQB)qvWu2^?PY@Pt7(;Q6zj zD`Tc}I8nmxLE?Ele_mKxR6=bTJVAv(>CSYz6SP#K? zo~}c;MS1{ZF&q_wj3JkViO&F5fCo{@0H;0+OrIZopib_$1!O&poggAzjBPaoOF~uVa@;-JUGZx>irb0+M zh+b876%7!VNe>c`0c*q8^l|WV?y^c0BpH#ccprXhDukqiu^YlM|977pi`4oL6jgg& z)totM;{oqbwf?dwUyN?Qd2T222|~Hr=P&bcJ@aA0pp6vjS7V27oLYZHly5M)sN1Ilhf=4Ir52hqa#SKr zP4d=+9MI<^RZEWr>2zW+ZI5p#o^y!D`h7`z)ilds@Z=k!>QSn;o8vyKlL{X19YP3I zAMZvPRkO|~;J5#Yi+-?fKaOWGM1-W+P3^tZdYbl3wC$qD3x9}WqLo>ql|Vjk<$%%3 zdQ&TE^RmQPwYe-YUTyX!CaBG86P5Jb^d;#ux{9iec5276wLX{jxvHI1XM8Qz_O1IE zWbQA77@UeT?T~Nst{#|I-M$gcxU@k|?N#UQ9t(tykq8tmLEFvwin@Q$EY$1GQ|tFS z)OKBb2C8Y@t0$@Z=P1Zy97_^+s9IT~N+XRiSeuAC`;_$j^jp%GVL)C{cmLj^+b^I_ zHP2CKte;n#FIKe$v7{vh>h5c9nxQy=7Np5fGET+LsPRrM^CPEUZ!yzvAo>K$rx zX}qe>LEkN%glV1=pVHBLPlP;33dy9sT0qrafHvz(K!YpTMO>}Qh>iLay6-ZRUcC(i zdD_m@?@P|#>(X9Sn==y?m$sjJqa6;c`J1j&b?t;y%pZe*w!gRI(4o`tgHfr)_Y-yZ zA0cH-3v0%%v96RIq;qD$T6J(5;=T*6)Yn>a*5{jqZun zR_HTK0~bf{1R_V1l@DRwa^4sXp>L2VU{RaX#EgX0%Mw#b2OKo~`Eo*#JfNy&i3v*# zB~AIzsl9IK=*O-!XYATRDILeKz@jT_NtwP-cmJ1VrS0NOVtlsZn&BOk;9CZ%4N7kx zfMQPVHQoL)ag%0wcNu2yB(*+?8wnVW;e;3CREcqF?5LTm>IZ)bOM`*`lhQkqG!Wts zR(J;#RDCZ5qNr)N#IF68_8e(3_*h0tT?VC=`h3e?(d(X&jg1&@F&~$Rxp*w)D3nlN z3=KSiMr{p0BOb`O{Si&Nv_4WqTLU}_rv2G=aUCFfv^@6CGF8h~h$Ss^7%Gn(rq(~N zxYGK3?}+}xAfx}pHu`@vW8P8JCnxXIuXqDiWcL2d)X6VVOs&6?3kj7!#A7>&q<5kT z`x>xNqLC)lJ}O9)pQK?PV@OEla=FDYeOxFJ@>mMnwAdJUmv%A*bY5_2?`vr2tK z0GG>4)Nzu_VYKGbdW2lQ4{WCPnKSL~SeyTSJUF#Ekjj3i=88#hY9nKunl~oi$wl*y zuiFsKi@N=3kb!7A;Un1^zemJFqy1+f)yAW~B>%i9-Trg9Q_?l>dsgmd)Hj;&o0fUWY1W8O@c2OLchikGn6QY zI7qV;LNMN9>d%!XIcp?YnpMv$q|l+QBMaU~sU!kgyR?5+k zwt5$G6-fkTsj1$vPHj!B+IZZz1bw`XHz|hfo0K*U*%~yBp$$`&BH+%ZD)k>l*_z(P zIJvarPOKtM?QX?6@NcfPRSCAnNBH=iZ;T5xvYnbqvm96o%UEq1k`q9XW?juy{~228 z`O;`>SPGqUX;@LE{-YpNy~)89a{<=fs9w9OErqb*g>6Gc%Dsf={?@4-3B;Tv(c7AA zK=mf@IUP;8q48P^lFs^%6qWkYoJN@t#BffqlM}2>IMzk$s%A|B|Ay9QQeR9WoJM`n z_8vQkqBMqQ7{6pygDXUS0Cb*PVe$iP4P#*U_!t&|mQ;9K!(SjqSf?3x#@>|&t7E$u z8#pdmA4GlJm3CKxFGbZx8DfSUZX{OHEGxi0l!7S#gervk+(Z(Fz2Z~g&e*j-%hq0W zk;9g)?GtJnn0%b;>>VcCu@JRjgx8t~%>v4)M<=S<{Ggj~1#qtBm9b9pl(hLlZ(#&A zs##j07B2sSDDEm$#AjpiZ?=2~NNTt_3nTerpx^{X>mF0nuPrti`vRJWC1klguw2R9 zpW~Lxm^vIr%AZy~TJ|t)J|_3`t_4`lv4*2PEM$Xp`zz#)z*bz&haii%C4-X-&vNZ3 zxo$r#q_P6*HZ9ra#rbFGTPHd4pi_Ftk$Xw7#%)T)`%e)}G- zK`^K@jNbO{33Vx=Kdxq1qGC1*$u8~L#5#;Kx1-14i8!?bPVFQVO}Brxh__~I{2dCU z9Cc|=CN?3(*YvNc+Mk67c&DOb?#@`l70Mg)DwLP6fYguF*n+HWU)<`(||DEGFS)Vc%+q1M<6$X#tNaIWT6 zQmtWnB>p;%uZ&k=yHstKZa)ZwVZK~sPBa(oIA1$Tc?kmub@@F=+Zrg;!Wo}Wq(U|*nhNRr z#WD;V^n^76`Uk9Od%4Y ztNAwU31hGw32MaUl#Q^4N>)xw(e1l3+B(kdmivBL;B;l2QZMF`;PhGPLAjaKF*F<7bL1RQM|)he{z7W@dA#| zNU%Ng&gg1HU0_2zvAu&-ty-a7jE>Y1v2E95^qd64gZ~?KoYZnv)>Z&yjzNr-;}6TK=FL!Aj6oPRLydf2G zacViS*_s3WOHbHEF$^?CfyI%;fM(q?Pm@-!mQ$dYXxFb1UeKlLJt-Y#lPtz}H!p@VBh~$mc#IB)M?~58fj`+VP8Z`Fuc-<~ZYo-ae(br?fozz!49cl6LYCBL*Vlhh(u1Qjl?fW}?D*(0;4i zzfg&pE3m{F!N#k&$T$H6B!e_Ww_Vzvtds-L7Le@v0jlcbcY#7sCg%JTBLAWO@;gL+ zZGZWy$p4x-pZ8zdh+3@8)}!~5Yqc2TB9aSp|5%ojj?Agzwgd8Q6!phI&|_De9q!*;*m zmAIF(!geum^Ae0bj=H<}it&rwyk@1RUNssbBE3l7(&F#S0AePrw7pUvuD zKRQH_hs;H|A*kP-aO~3eL;DA_KL4H;Vl2M*e3sFZ}kGjr@B={!}CXHIe^Uy_$nc^avgnPb{otpd4yq4`qVvH9 zFZzHiI>A)*02K|{gJ1xM0JBs`DUPn%ZWLNobdBW^VUXjX&+}}55W%rQuK6v z36@8iLbir?d0(N!FM?X|0N_`^rW|*s-KE(4-$vT0&4(+6HP1K|(aMIw`J2TB zn*KDXU}xNkPKl$DHN0Ct=R1^7fbHFG>gPMucZ#BgI(3*(U+G@Qlf2D+C4u`TcC{9PZQYbJI~<$8qfb-fwbK8MK$C{bHCr zDZ+$f-jB6jHSGmk!;iQSZ&1ihwKX>2DN}nl6H%wMTOh!@dG2fo5PSGsoSSGvW3u^4 z3WX=JLCfuN(?A9rAlI)Rl4 zF-ccqjzFXDy8U~|0e)zs?!yY@y%(X(p<;!w@YRH5nnyA%Am62gEO&6X3@6GVqcmi4 z5o<#Lw@2=Xb$bmYWbA$u24IAsYoO9e+l><1Rd_JZP{z=zSu}y^;55x*0or!~0y?yy zS91IvaD&CZ7N{R}N& z;|ucfs7ir!Am;=53?9M8K{#-_eI}Jch)Fw=t}llAi}SB9cnPTgVXz=}&%1zfX@`x2 zXQw|K4E!eW*S9IAflXni)<>I4xCED=PFCY_Thq@`h5Qew(=bG`r2{(<+1PyIcmmkt z2KGa+Z_@X-@W{8i7xL14Qa0|!zS*?5d#j90{1qYOGT5Pt92dTIzK(FA88;b^Cf+W> zBUiFxZulaGISMh% zRA3|(u+ZIT0+kPkFWKyM|wnt`i zm8uzqfM#Mr@3%~Xe-c^gzaoz>U3>o6F^B<&C6s&%NlEgzfp5ThI(CR;gjfPB9-j;! zU&rlT1)q|B>+ove(eEor0uvy*!mkU&7^LlYi8SN^ey$|`wG+Kb$oa(`q=pu-LGuU$ zGeRY|8%7{6UbjDhHdO6z&?p2|!DMd?&;5dA-Vn_P4IZ*iwGpZT1)4;_{LZL%9Iij= zsie9JiW&CC&jk)y{+XAgdF!*JYj5=y^P+#?Snk!QibD+Deoq?lf{R55)aD^DiEeB{ zsX270f)_^0?U943q1BX#a}^3Rhl^z{^N^S+_4Io@9vqw_t&zNk_k*gux*Pse>5vTVKU=((M;< z5uV^a0{4eZL%sc4#&)3+irvkNap^DQ)*9MSlR*1uL};kMM<`1sxc)@cU0&V(Hh;9% z0bl|zPN!k+3~jsp_F+KrHTri{G8EK%iXI~la5wQH!5H87@EF_*1vlrq{We~;G1!4r9+bO* z2|8wR^a%UxzybIvVFF{gjPp}mC8HI$AmwPb*6q@;dVQRYaL7=nhE2SF#5wRp?+B;g zp*#IGX!1nV8cTNL%f4LR7ugGkQ+ovqa;oaLOs{7Q3@2la#^AzJcgFs&<^5lC?-~0K zM$g#)qtyS~ssH@S+2O#WpVBNpMN%|~5&Wy#%-cc!t8^kSau6|GAOT_eE%R&6)RFG# zU$rA|;b5|#j!2E<{gM0alduD2chdYFg_imW4`5a!f~4DjcnuZdV{Zxv2>F=kt@RPT z=Xqf=7^j4z40`j|c<@4R z*6C{YU`yalOx5jMI904DlemQVf|#RXa=|4|#=#i;QeJ}AEAORFZAq+u0gfp;ODlj+A1%s!5z@FUujt5+MZY6Kv|?|}v5a~EK1iTKNP)57B~h?fRq zr8ML)9>+Ry_9%xR<0Y34MfKOhJ^2K_m#wj#)DibNVPBH@y)ka<5sJrx`~%J*ZggVr z8Z>@ZAb$<>$h9q*E^dN-+H2(Rh4Tpk@J0GaVEJqOjBgMx+neGW0|T>-cMnH^urbdW zbb4`vz_Or``UIPen=(2=DYNLA8hwbb>Ko8k((dr~$R!W9e#`}D%jhdXm10d#gA0j_ z)PL7nuK%3!Y4x?g@TrzU%$zbGg17OK)BTRWWoZGI_6hFg!KAMqiykhgT6BHevWQZ_ zdmo^41MW~;mzM&IQOF?$%cH?6petrc;v6|`x2@q}Sb7w^zXE`sza^d|k9#Pc-h zH_snO&zgNG_N)AmQkyFL0D3jU*3d^vPkNZi(Y^`U+HAVZ{UbgxtiJ*lrz+4BjMY`( z7~1riQDrpsfLsbYNTnQg&X}Wk2Wej%)3`GAy0mu@f90FrHMHqPGw`OdH;#toT`Tsy zx_ug2kdEe+Mx3^byNuhfLJnq&h|7b;hloY}?#FFSt!M!kD>dsa!~*f=-~GRFSH}Q(;)oc5^X*Vhmayt3$YnL68Pv({9?x(UT1L1 zQS=VA_R=&M!JEQRkg^rf6wV`E%pn(EF%2Y-G#lmSr@rJVsMqka0L|jZMr=R zeCzi8TnL?f&IAzm22V6AUm-zaOAU?i(xD#~KWuaOzBd^XKd#QGR_=1s#@vyWa@@Et zaYyQ0y8cYwg|QnYdCcW^pv_pP#PWl_VA&L4gNJ<8%G#Z@{d6FV4#(pTK;mS7h62{0 zUq`&db$dFPQJe2b1v=qELQ_ZpT&}10Z|DY1Ds~6NIQ(%dQHR**Tt=a@dp;jY*}26{ z2%Z;^qtwU4FUN4rNx93BqRaD4a#CE^^V7^Y#Piej*RaZ5N;uw;5Y867*R)(&yFchN zo-uqb?8TbjhuI6s8bwT0f4W26pDy~@oDQY#hB|$}j(YV8nXdim3Pb>TSnjPOnyQ34Js#dT%6Gu!w8mms13B9QI>%+$l)l0v{fncmUVyIDOb=Oy}-E!-wcU zOnLB>?H~v=>b3UMGS5|nMl9GJV;=E%eSzVBy$&bZHw=z5cBI%Zs_>b!&fsq8x1Qo| zkqa&T=2CDZ{6_B;+1wn@gHaeBIEkUd_L3H;yQtUPZg?pLfBNRVV3R|^A#CSEML5vU z{Y0pc)Nkqk^Wnz{5gA=54h0v%v#nv9p$A(ggDe&_+DP4?+aKhQ*69F{Xbx7X2t9bt zFo5@?NKnOtQq%3ZM8Qx3^%N!eE3c0YdNj%zN}xgs-a|&15*!YU`zWLY1$?AGn=^vEqaN_v522Y-t$2l7r7<+CKO89M=IdSqSzGe zPAHHrc4ueWoy;ap%QNlHGRZ%Cn#47rk6~ zZo+f6b~#Nq1c+~sRhyqoYyr+m7hGHUjjiwi{}CcU%hU%~5cM7?5F#|&ffWJ%J3VI% z?!Rd)y_mjlfC8emy_Xxo{Z%}czl)Q%BeQp>DYzd^rH$bJe69x{qh@agZY6Q2h2kIB zWl|ul;id&)^c%Ow}C`#^8 zQw{}|jQ~IQ?4!}8u=mXv^01F%(UPU=Js;HZx|Wzcl$N9a)wbs8k%KR{=3{R?)7ZzL zU(&?PIb|Qd@d&xoC&-eKZ*wooa^L2m%lOnBLE11(x8IvY5+4ZPvlsut^OikdF`W0D zdX^vRcX)qCE&XTrcWC*U`a24&_uJo}(2^MH?|mrC`@0Ptgefxmd*8&<_qXc2v-fwx zl7Du8zdU1qCxG>S`}=Wte>c%MlJB|;9W?cK(}dIa_ulx^_E+q^>uVM43ThL47ejMY zqU^U{W++e_8sKITyED9HposfXwAXWKH^jsDjfLHyRz*zvLONVI61cCE8`QC;X?Z>k z1uSy#CfBJOps%>z{hT)&T<;#?kJdQ=SUj3&4G9k%{)Q|$39S`HPF*#0dofYyf4#e! zuPIpXMxq>q?jfg#*1NI1E$;MS0-=q=fvf(DuXpok-rdA4+aO6C*I!{McL@@c`9m&c z!PViWt~=oSO@zDi=6JN%-%2-;R}Nq4Mi9=t(g`z${r|3>fynaT;817WkP0VX!-cc@ z-52v)X!>}nXR>%S9%uDmU-J?35&r8Qjx_w&hJENz|NiTTi9{jjzrMvEt#<=p936bc z3kv{SY1yE0d=o`Z>jGlGM?{RFJdOX#Nx*+~qMYHnILLoRMwshzB(U-id?-!+>vfX2 z@LxX?B!uhIj0bSNj?*{%*L~O$g!!*SsLEIOUokIVzXkr`w1Dsrxj&msF?{&jSdB<5 zdsG}9Ve^kyBT)QW&PQj^b<^#6m+}seqkF=6v|L<*Nk0Nel%B>*L$LLD4j$Cu-higk zVcK#m0M4{dZw89q2tIJI?VXDBA7~*&dIIi3CgAyMPR9DHONbv(yE>d2Ncf^v3Jk!% zYR*XD)+L!1+i-m=P_?fo7z^t;1ibmGnqzXQ`bdn8@QBKch)JZ>cQuOs|+@d z)=4BL>QM$NL2HcY_M~yZ2cz3H;c;xTc|c*UeKN6*hKL8!`7AqKf29|B4ld-<$jyrM zARoN#O#STuucE&`yz2n2ICo~&xZ@Zeot3?5&9WIP)2O}|xJNfDN-(b8E0+|OK0MKDpY zzF~mn8`h`L`ypnHZvO)>eAO(|N{u|c>LE)jOKSHe&}i#5sEl&*4r;K#SlNl1FvTMpaZ|pt(ork;&L^3IX3dB+&|-)$8i6= zn7)rLARgr?Jt1)0%BFC8E>?AY`VCcNLppOAN>%uLE$(VggY>n2+T_e3$=a9 zsqz3ADG`1$q=mv@K?TMA$AojcFU{%Fedz>P95gKMOWzOrW&!rUeuxD_tRirt{Px`# z?ksJ$Zl~xKOkeo=@e1|EZ~v6mMQ$Ha=+yP&cgw~4F&#zEi&!(h3;M9I>$q58VWYw+ z^uJ~Vg@Z(Jd_(%Y4Qm$Z^F5#^T~w1k{|sD6ef|I@{ZxIv6^0K5`q$@l&hQ!ZS;ppf z--z~cp#uAUtDQDdNMDQe;2e>@9O+*!6KQ&j;hqbS_S;`DB>FL)<QS(}T%1%F4O z2N{M8csxV1F2{mLdUZaHySm@v#4~;9hiSe`bT#Wu5k+W?G>TM+BCBK(`0$*{BBRI< z+45{EN8u8qd8zM#KfFppvo+NWq&`?z=aP{+F`7h;b8&(E7&6f9?@Hu+f&7x2v%?ZO zgOHkK&=^id$p>h*j4q&hkI5pVM7?4N5%DgGj1tHvV97A<68XUZPUQ#l30WqQuL)$< z&4gSkk$VKP?ovWd<+31CjW`?6u&+S|H-=MC0UZ4an|7YxO!=Wxu8D&098Twbdf+=G zTSK9sGsmFwN6b;s*>BKE52y1sHRHF&aypw#bVdt0HiOQO1)V1hIwxT|O#FOb&{1KC zbo)(2hdT8t74qAEe+d9t_^$~fpO?z~Cn;aEUJjx#`Zs42uslnp{q|o1==MsgL8O{_ zd%?E`CsKQwwGWg~^PR8pn)iyD^}OcQM$HA`H9sW0X8oh6`ArNN+EhhNC$IU%`Lazq zJu(H9ekN+}5H%lP#B1WW$cS4TuesZ(c@*kys#%Mg&-`T|m9##vg8DU!O6r!an3QOK zBh5{2311mUscz^S3Xn#Bmni9$w}ouF0npk3zNTQK=i#MlDpBG>!OtiFlvu@ALX?;< zN@SzN7xQ>yp8}wSkGF&plSGLtP~xFHUgACglqfPv#EB9(C&B$be$`jAEGa=5m}*>f z4GzkED2F+9^J%iyh%C=(vX&u>8z0;u*2k}gsPys4{Qm*`|5uT(kN=~c{?7p>Xb^b9 z^uA-7^`%VMp?G!o?)a6TVNv!)?NxUlh(GoqBv0q9A0#!t?442nev~(f{u@>Qd6YL! zTumiQ6Syk|;XzAa0f6nP4sLzu#UbP!vtbzMJo*9tJ5k3HNHgG{BK(O%>#(1G!I|pE zDF*@{fk*@Nwi6WtJ_~+ft-A?l9ge4?>Su0*Yw|o~5pvD3nDAds5T@W}j z7*C%^E72jip0;&L(FvT!)HKJ9Z15zb?5Gwe0s;cf)xk9~wD z{5R-yzbOkUTsI1Gf|%p5XeaZ|kHq^}P(iI5;rL`SNIyjlNR564;BcVjueu(}`o1~K z3gr`7^?H==8W5eze{P1VeGHM~dM>mo@I1fHXBi|K_6{@3==KIQi;Gc~dVQeJ5o~da zsYUN)VEIK9BbEpI^P-@c$-N_uvN#6~a^4%0a+Gp~2VH~FaNso^{zEv+{VjselfHot?z5~`V?m>Sv<$!KqMIlfMMJ+)>w-@lo&SwTk zGn!ZAe2&j09K8gMVM*NubwfE7I2e^dJP!EcWl;KW+yXt=hD-`6q+%s2r==Rl8>wo2 z2d1CpuPA`-FCjG6Ie39o(t-3r1csX3Z}}loG?x1MJomp7hUyNH<6j;F(OXN~q5n@3E^m#&h=piWuH%XV#6}rE{66yQv z^#N;tLW3vc={yqwqYu!q0%>&40`Xr=-Sqb4hps>>ZLiO(*1HqXN_GN%w+J*;Een^A zvT%Ls13ckY%vF@`7HNOBBAz?wIZHt`Sk)dyO+h*(D_%|6t?r^bHuSrs>ZN!{2{onK zGIvAzS;PWnysCcIg~uuMh>>58CFFx7QmS~he&+c&6XBlY8Z;EL#D^f(<%OuAk6(fe z*c87t8_&o72CIr=tQ(Qu8-$=2KtL4ok4+5xQ6vVSE%=VqR^SO#qD_Tc)uP8Iqd|%| z_2SZXr*~vZN1tl@i8TQPeIHE~Hoq;Z_cO9%RJU5x9Ta#9_NaGs;AT9-Y~C->(NjBm z2L=3iXp7dJoIKAX?H+{8S3Swqg;uwQFC zwg+{2U%$zT;6-e|HKBlTSoeMjT;K&k-4ggYFG^>aqMwh;=VPr%i1}}O%JND+m^ttZ z$bi{0_cmGf;yJJyW-xFkib)BK40y!DV93jjWbgayq<0atC3Q2KP?c$~`&QMTj9PPR z;13uCjARmi$tV$ARuN%4>Z0q!_3iX*;m_VFkaEgOTczbUG|%cg^*`J0wxpu~ba5^K zJrcy@B7o;D7vldYE^+uFyFdhMF#h9ufaP&_d-hHEb%hxg_rq{3YSbd@1|+cfE+#PW zg?nbu@74P9+lRn_telaa;LCMgvB&oGjrdWjJqhZ-muyd`$Eg0HbuRx<+-4h@=uCUb zi=Ea`SK98XIGpJZ-0e#1sDd%vlhpe*A|hCzNK;1E-=4_sq*NUAG6rw}SJQ;OLf-3M z4;+DLQ(lH#hBO@Y4N|{ppGzi@tgI|kH**#+Y~AbL=!TblLj%WA0qg0X0E`Zkj`=Oe zP;77gcqf$>?{h6y{W}sJxOI$^NpeuM*QKTYG5icP8Lb_B@v>bY!~zJz>)_j;-Iw0p zMXUExpy?66w}Zl}_M-rbss0DLk%_ed)=;-kq#V$>&ai_19-hk!g)AW^yFWk~WDAKV zB@|j{K8hn^jh&PUYnh`qH&Qdi2uG&3Z=hEBTG7}HD{EjeFTfI?gZ0q?m`gf~ z-w6Xvcmg~EN=M@k)t}Xe>sFLjsnKR?6s8tR%%hK=pq_o2A%+wBQ zeJKah{TWeNL%M96((n0rj=RFw9G#dou&p#6&S~(#(RG0n9sYriZ{7OWVmW;a(|g5>;G)r+?_JZS(Hxk(rw1ci1DR z*LOx`Hd|gsvUd=02asroH^(yl>KAE!YbOxfU?fu`2PWEJ!}iR(gRA4RhU`Y&2T^6e zr8`wD^O?=o-ymsw=D$YT>c2~bnyno``~sGhHD9<(G(?ZiI5l-fQ>zmTDFarRzIm_gm4R;QxPv|KG&_U&sHypa1_L{y%i6Eov`li*+$u zONST#f>V1b0PQiazU0egX<++}^!j&CxU>W5$62&Xd&xT@U30`{7v)5M*6kYjl3LX1 zy7VPy(cZb*$aq|>!>=RYc3np7@yw`!Ht!JIQ-Jk7eXfC>t`szL;O1jr!_^~w{9cTz zKK@7eA66&w6WY(|J&}3zY|0;hE9Ra<-dKEpUABX4-tuGb;eD1vCbSZG8JP7vb8jd^ z6i&hKzz7_+HTdPSC=F8uHii@cfd#ss?9#G4+G6--*#!ab2|+0ta~e(X8Yu_vNyWh9 zKIU$1H}H_jx=Sz62giR9uK*H@3J|p1^622kf2NE(l&ch{~AYrP63nh$|e13kv;OBh_yCv+D zaHoVnknmm!t0cTd!W;>&mvD-NiiE=?{M+|Ldml;IE#dPLwn_M)gmn^@NH|-ZU>zGns9^}h_K4^tZUDXU!Lu0o@gD{gfcc^Pw86jc_NR;(gE+*MVTRn>x{ zwF?$3xVG9$Mfs=PSLQ7(E-ms-$qywWnY&_@w?qM%G-axSKde!OCQ7T^#YCr)OQo=Cm5(=8?yKg1X`@gnFZ6gY9+MRM z+Z*HMXh6Rp+S1C3!m3)OsHCuBmAhCe_En*vQi={1QXY|I`T3&R{1Ws$KR?8;dED|* zr@Wdf_LC3U`P`xar)uhV7#$jGPKK3M7nfGwT8TzQg`k|m$~&puC8h2){j@s|^mukZ z`AaxoqD24g5Mb6^mcdUN&|6PeK#>}vTvO3Y0F0zLdnyX3)0Q0s;uE61V5z} zrI_$Zmnr3?)zxGet_m8Q(+$P7%0!IHMA6I8|3zL5x-?YPO0XePh2R5k6_yny6$VpS zs_VFzl!@c3Ctgp~@W+EDdKFJ;(QRdJm@!4HH`S-_kE!3G9tobp`7sqFj!ci|!V^*S zRk&+BSovT-y|o@U=@-m@wP8M4XmykASC*BbIP$BfU~02$VV=$XAT;B`F)~p#+vhD@ zQRa?BgMKleuL}fu7BmoeHZ7-u9vOWpkjSpe>TF+)SnoR}H1MYNVhvVc1td{=#Q%x-|6CCj1SWjV@Q$m^#U7LKpLi2zu zL?%ip&2<_-DG1-GhQmT1m|v7 zir{oj#v!h%g|&kgq5gTQR=NCANLeXV8}J6s&qA3t#Y@wq|%t^BnMP? zLn4@O+#10PP9k+qD)nAwHX>QvUr>b6@P;Ow!mT8Xms?C+=sW*oc(NVCKlfp>bJ@UL zk|h&v1(-9&HPQ|T=c_TdXvT}_d0N}hAD#a2l0K)%jW(n_iBweDA8cTPW-ZL-d0-W> z%`xN}RG9`+FiEN$Ua;D!fTmS=B)NG4ss3-C+snHZP+`}5;X;DGyJ$DK74=g zJUm;!Xw6lG?^0Us5nI2|ZVIv6VmJz55Mt#k3m3F-dCb$Xu!0s&_v+G0U$v0Dl$?Wt z1OE-5*p~2pzR^TowTuHuXyAkJzlon9{m4Y)>G^Du{B4l1MZ&ETwn<3jjKA$j0q8uO z%M>@_D|f~TzzwJyP?rGQB&0I|6+j#OcMabbeCZ%~6~5E4Pq-X=ie>oTfrv*PzAsSZ z1BZW~&SfkICj%Y0Ig?w0Fh0-|N*TKka85?9i^}xnF~q zIs#Y2|Hfk7K$Iz^zmu4FHPyoyu~e#;oLsaL?bG{~2)wf?84Ga)n~_dsI>K=`5w0@~ z=j7$O!g0J@cNi{*m+J|~@p6GMTrMxy2OPR8dCBGF7;V9DLTvo)NCp|xhFLr(uC6Te zmeVth%QB3Q#NU4V?ZqodydB?^{j2g8B|6i2JDp*;n|M22W?Zf)*Byov<$8dN$N9{Z z8u7O$jlaD)#VwxcZ*b-ie?sO{kz}pFjSS&Z2-gmrX>6tuj^cZ7VlZS~8eS+7{$3|8 z*&2Tnv*%gHnyCF1)sPlGUE*(i8h;Cu^)OZdH2<5IiOE+X$qITH56Ma#=ZWxV`$XYf zEN1F}Vt zJ)=6JM#i#{z>freB=92_#(M+~>D)oU#juf6fg2IaM&!n^5z_{<5mRk_)}A2+<17Q1 z#T&~yAheJ0&1_<99GDG%#$ZR3i5e5f#&`#_F~zZL4B8ljHpWaH)e|*#C>vXhZ|)E_ zc3K=8J2jziRL{t+;T>q7=M6)i6?rJL(3CfBC>vKijEzIyIOL66cwyfKJ)^tM@34EK zM#r(y)8g6aDtq6Ep7XkF9ahiKf+0Ckyp9cZP;VOQnd=C7dU*f9+xkQtZ{wTXjQ$Oc zW<$ZZ1$-0k=lG@)o@DXfVE+6Ha4y0@7L;#(4y%KFGwmo_46_vvW;Tq2ZQ&446t1qt zq5Zh2F`lS|IF^8MO29ZJAU|R1$iCq{hFl<%T*zb^%$~Qlo>+t@A4)xy{i$0XM&cwXr^`Tpk zPdE;|n#yn*3uF4Cdj@okBVE`R%|?1Hkn>O$2R*Yu&vFor_#VDra++~))S{uY78ols z^te|LQAUi*VB#xh9F3885VI7=Fpq<=E%@eWhCamd=RLqJBODX?p=X*KgTm{fjG2y* zbsS_3y&#<+-TW!$I@cA#3tbsK*21h&qZscg#%dYX9S>-Yn;yre?;Fl06^~>Iubzi- z8o^?7?W`}0v3qA@$R%Iq=h0)L*^p17Jp&5{BnRn`enZwBa~V5{FP{rc$lr|P{4F&2 zBOS2`9pSnh%ibJMf5 zcRA;oXi{B_8v7H<4Z{VznWa<{{iAw#ULv2bMEY7&K0T+I@&}KBEdtMDs$h9MBMOKv z@%wd@S&G{%w}K8q+0Ps?4ujB7%-yLNpTSXV@CxL|Mzh!|A;TzZG_zv-tk9RiWRow! z+9c_Ir--r9#aKQ-mtfR@C^jGm5Vw)+)1G1WsCE`Z&wRYm#x&^e@F6s`oG%0Nddc)8 zK6Ic%sY|ea5&yj>Dlo^xPlvd;ymKR?$ZHlM1nB~+u zdE78q2sPTiL-oXG#Rk|Aa71gR@%b&C?E;r-DmUQ=SWAH`G2z|9pl$Zw<()T_g`d^A{ng%ib_!T-YhI3kK%Y z=9W|;__2tNQ$fJXUV{xmI5dBe+q*yhRVva{Mm?Ux+-zFd^jqnRtCrOofPY3 zi--~e@CFa_aVGc_N>TCr^y)Y)uHO7FL(A--cvApN_mn$K+#JDY?j7<)ia`C&?MHes?}e zxCP}Yf+q)ob%*Ms0>Wslg`^81p?Hm#c|+|9iB>W8papG!o$8W&e)4v6G@eO#iz__f zQHrjb&5%8XOnpW__nG^-7=gi6h_UnjJ|#JsU0Ht^q&r1 zaD?~u5Ot|4azNNmWzRDBN#$mPa=OPu;{Rr-o=Gif_93=G?TYiN{EQMe^yg8~;^U9W zD&|M4fg$x^hs-?@RoJ^G%@>ki8fudymCZx3Y;6eZtai%7LH0Z10q3T(tK1l~%kf0B zd6Bo$Ls}*xZ3_)q1nm)x=uV@jywM*SdNx%^+E7kbMw3OzU;b7xfI-~(3T zuY_||R^I0G7_%m;!dnHcjp*97jJKo6TbR)$Wq~m3&C@Y0wi`K5j{^EL{aT z=HtY+s&Y+!Vb!YDxIiyziY^&h?uyl=Rh1QVuL;}huo769YH&+|AB|)G!t&iU?jj!? ziLi`OXphR_hKHwga(OjvStb`%c_&wiMRRi5Rg^|6TVk<6qN9S)yvyYask z_u_Kp<&}8Z-FS=3*c;NGkoMam^2!tZk?`hE*L5O3-yNm;y$(M=pv9h6pyU(q@!IvlLtCctEohwY1 z^w&$6^WWUwx$KfOt` zmtbU^BQ)Cg%n#>ZsTa>@BV)+FAXSv_PyPin!t*2LuUs3RA1VJnnQ!zzQvQxKQQydq zl>hpOd_(?U%JFN<2yeeY_TP|CPG&fLV|f`t5T3V`V;K zp4^TZ@?&!RM;iHa!t*=s63=60evcZS-zW7?k@;QD@O(wqS7kopro4Sae(h4flVpC* zJdtnouT$DDLq8EK536tJ&Dr!5*8zFGHdlCkkL2H=(;tjnAB=K6lCEMPBvf)mI}zwp z9^2%)Vq_@WMZLV1bE|LE3&KnxSylGSBl%b_VY~rlg|>)wEN9c-*{-BlC+GL@n+2WE zrM(y`pM!k&<9z&sIyB^26^)qsOy0(dWj(>1`66E;Qr3-rTHwcJx8Lp@C zlFz=G;@J>a*XHnN1Aneik?s7e$H(wba;^~k8~#Z^+Mjdf|ErekiICq{tshywC1So9 z`7P3KGoH^C=16}nvi#pm&m-jg>e`g{I+`RR{dfAdw5er!@?#lKU& zkz~H|v%^@aL`{d}qo6;2q@Al9FXY_z_u7fC%;-P2Lb6xzZ&2-TyxRXk`)bhtr$M>? zdFllJk@nSS$FQ%)vjNxlV_*9o4VQ;eJ`(mw|KeY5UkyImq`zeNn+E=Du)pzj{4bf0 zY{I|yeC%(0HpuZgWk1%ReTq# z)&B8EX}?pY{XX0NC9*+JKl+pNNVwib)-#?vaz(j+wf*jIe7gU^`P~!Y59T~}ZgTj0 z=eHq81J*^fZ{Yu3;RdLTa~r3~YX9(abToI395OET57YYx z2{``6atKKKZN#(AmHqz4xBVZSzsYj`80&M#zj%G_x;$JD*UR|Fxyt2#TDdMczD7Ls zT>0y7d`te9_{Vks=KirUj@>feZJZYv_;ZCNG9G1|=bUT3{`4cVevXX87L44OD-2&J;3hz50-3E9mHfdXtrr&4I z8!g%@033UPz$<{?8;h&>R2OjNg`!;#;LMBB7vLR$d&c43KGI!)S|Z-xX%2sm*ZQ5G(j)EO-K`5i|+=c5yN6%;7hW?{+W64r8H~>`&6dc zMBH}3m*k9?7}N0OvIV?63H<^c?D5#p%Wzj3X~a9(bC;u!NMmhbldceW_=)UInWh)k z+NKDa1dm@Sj284y1Dcx8kiOl2sev zjj4h+oF}s7GmwumI6GuxuN7&W6S60N(7xYfX_4Du!7RnQJ;hTpv&JfvVm*4^M zI94)W@QJf2Hh%%;DU|_C%R%3f#+f|?Cej3-muZ~E z5Z+m!G`+ik^LI`s`DXM5-}TfkU<|5ZCU?#p) zh9JFsN$EPkgKqGIGKTAdfz1%n%8T|IHu_13uS=vUs#SKtR_K%k;}=0yCS%C*(Fl3iX{J-NKJ zsH(EMa;0~2QDynHh1KO#R$ny{zlTs!x)L`SmzY~4Qdb(Z@DR!1|Mh1N>=R^Ax4CKa z`pp|Qw`^|P9NQY-%67!==zhHC@eNP3Jkj<<`x7+slTfZ{^8*?`0Z6Z1~o0>MQ-?U-V)=h1j+BbD>>e|%3DX^(;(}_*7o8vbpY);yo zyg7Aq&gMMM<<`wzo4Yp$Hur5l!MROnO=?YUO>I?Mb6WFSOIkgxHLXpp>svRpZf$J? zZ-TQxYhUY$*4QoaTN1V;ZAspex<%cRvn6j!$rjI+nk`LR)^FLcW$Tud15DfLVcp(iNF(m zPp~#FS{B<9-;&T0XkibL|uAithXNt+#;J2rQ6Y0*ff zwmP_sI$L`nhlDK--uI?0d5;vJe;toF9_@TIb*p1*6ME9QwF|vqybr0{)NOg&61FSb rOSac-k9{ouv82b6A4`4g#A9qn>JG<_4LjO)bnU>%|L5~>CV~GC&w~Mc literal 0 HcmV?d00001 diff --git a/lib/multidict/_multidict_base.py b/lib/multidict/_multidict_base.py new file mode 100644 index 0000000..3944665 --- /dev/null +++ b/lib/multidict/_multidict_base.py @@ -0,0 +1,144 @@ +from collections.abc import ItemsView, Iterable, KeysView, Set, ValuesView + + +def _abc_itemsview_register(view_cls): + ItemsView.register(view_cls) + + +def _abc_keysview_register(view_cls): + KeysView.register(view_cls) + + +def _abc_valuesview_register(view_cls): + ValuesView.register(view_cls) + + +def _viewbaseset_richcmp(view, other, op): + if op == 0: # < + if not isinstance(other, Set): + return NotImplemented + return len(view) < len(other) and view <= other + elif op == 1: # <= + if not isinstance(other, Set): + return NotImplemented + if len(view) > len(other): + return False + for elem in view: + if elem not in other: + return False + return True + elif op == 2: # == + if not isinstance(other, Set): + return NotImplemented + return len(view) == len(other) and view <= other + elif op == 3: # != + return not view == other + elif op == 4: # > + if not isinstance(other, Set): + return NotImplemented + return len(view) > len(other) and view >= other + elif op == 5: # >= + if not isinstance(other, Set): + return NotImplemented + if len(view) < len(other): + return False + for elem in other: + if elem not in view: + return False + return True + + +def _viewbaseset_and(view, other): + if not isinstance(other, Iterable): + return NotImplemented + if isinstance(view, Set): + view = set(iter(view)) + if isinstance(other, Set): + other = set(iter(other)) + if not isinstance(other, Set): + other = set(iter(other)) + return view & other + + +def _viewbaseset_or(view, other): + if not isinstance(other, Iterable): + return NotImplemented + if isinstance(view, Set): + view = set(iter(view)) + if isinstance(other, Set): + other = set(iter(other)) + if not isinstance(other, Set): + other = set(iter(other)) + return view | other + + +def _viewbaseset_sub(view, other): + if not isinstance(other, Iterable): + return NotImplemented + if isinstance(view, Set): + view = set(iter(view)) + if isinstance(other, Set): + other = set(iter(other)) + if not isinstance(other, Set): + other = set(iter(other)) + return view - other + + +def _viewbaseset_xor(view, other): + if not isinstance(other, Iterable): + return NotImplemented + if isinstance(view, Set): + view = set(iter(view)) + if isinstance(other, Set): + other = set(iter(other)) + if not isinstance(other, Set): + other = set(iter(other)) + return view ^ other + + +def _itemsview_isdisjoint(view, other): + "Return True if two sets have a null intersection." + for v in other: + if v in view: + return False + return True + + +def _itemsview_repr(view): + lst = [] + for k, v in view: + lst.append("{!r}: {!r}".format(k, v)) + body = ", ".join(lst) + return "{}({})".format(view.__class__.__name__, body) + + +def _keysview_isdisjoint(view, other): + "Return True if two sets have a null intersection." + for k in other: + if k in view: + return False + return True + + +def _keysview_repr(view): + lst = [] + for k in view: + lst.append("{!r}".format(k)) + body = ", ".join(lst) + return "{}({})".format(view.__class__.__name__, body) + + +def _valuesview_repr(view): + lst = [] + for v in view: + lst.append("{!r}".format(v)) + body = ", ".join(lst) + return "{}({})".format(view.__class__.__name__, body) + + +def _mdrepr(md): + lst = [] + for k, v in md.items(): + lst.append("'{}': {!r}".format(k, v)) + body = ", ".join(lst) + return "<{}({})>".format(md.__class__.__name__, body) diff --git a/lib/multidict/_multidict_py.py b/lib/multidict/_multidict_py.py new file mode 100644 index 0000000..cdbc328 --- /dev/null +++ b/lib/multidict/_multidict_py.py @@ -0,0 +1,526 @@ +import sys +import types +from array import array +from collections import abc + +from ._abc import MultiMapping, MutableMultiMapping + +_marker = object() + +if sys.version_info >= (3, 9): + GenericAlias = types.GenericAlias +else: + def GenericAlias(cls): + return cls + + +class istr(str): + + """Case insensitive str.""" + + __is_istr__ = True + + +upstr = istr # for relaxing backward compatibility problems + + +def getversion(md): + if not isinstance(md, _Base): + raise TypeError("Parameter should be multidict or proxy") + return md._impl._version + + +_version = array("Q", [0]) + + +class _Impl: + __slots__ = ("_items", "_version") + + def __init__(self): + self._items = [] + self.incr_version() + + def incr_version(self): + global _version + v = _version + v[0] += 1 + self._version = v[0] + + if sys.implementation.name != "pypy": + + def __sizeof__(self): + return object.__sizeof__(self) + sys.getsizeof(self._items) + + +class _Base: + def _title(self, key): + return key + + def getall(self, key, default=_marker): + """Return a list of all values matching the key.""" + identity = self._title(key) + res = [v for i, k, v in self._impl._items if i == identity] + if res: + return res + if not res and default is not _marker: + return default + raise KeyError("Key not found: %r" % key) + + def getone(self, key, default=_marker): + """Get first value matching the key. + + Raises KeyError if the key is not found and no default is provided. + """ + identity = self._title(key) + for i, k, v in self._impl._items: + if i == identity: + return v + if default is not _marker: + return default + raise KeyError("Key not found: %r" % key) + + # Mapping interface # + + def __getitem__(self, key): + return self.getone(key) + + def get(self, key, default=None): + """Get first value matching the key. + + If the key is not found, returns the default (or None if no default is provided) + """ + return self.getone(key, default) + + def __iter__(self): + return iter(self.keys()) + + def __len__(self): + return len(self._impl._items) + + def keys(self): + """Return a new view of the dictionary's keys.""" + return _KeysView(self._impl) + + def items(self): + """Return a new view of the dictionary's items *(key, value) pairs).""" + return _ItemsView(self._impl) + + def values(self): + """Return a new view of the dictionary's values.""" + return _ValuesView(self._impl) + + def __eq__(self, other): + if not isinstance(other, abc.Mapping): + return NotImplemented + if isinstance(other, _Base): + lft = self._impl._items + rht = other._impl._items + if len(lft) != len(rht): + return False + for (i1, k2, v1), (i2, k2, v2) in zip(lft, rht): + if i1 != i2 or v1 != v2: + return False + return True + if len(self._impl._items) != len(other): + return False + for k, v in self.items(): + nv = other.get(k, _marker) + if v != nv: + return False + return True + + def __contains__(self, key): + identity = self._title(key) + for i, k, v in self._impl._items: + if i == identity: + return True + return False + + def __repr__(self): + body = ", ".join("'{}': {!r}".format(k, v) for k, v in self.items()) + return "<{}({})>".format(self.__class__.__name__, body) + + __class_getitem__ = classmethod(GenericAlias) + + +class MultiDictProxy(_Base, MultiMapping): + """Read-only proxy for MultiDict instance.""" + + def __init__(self, arg): + if not isinstance(arg, (MultiDict, MultiDictProxy)): + raise TypeError( + "ctor requires MultiDict or MultiDictProxy instance" + ", not {}".format(type(arg)) + ) + + self._impl = arg._impl + + def __reduce__(self): + raise TypeError("can't pickle {} objects".format(self.__class__.__name__)) + + def copy(self): + """Return a copy of itself.""" + return MultiDict(self.items()) + + +class CIMultiDictProxy(MultiDictProxy): + """Read-only proxy for CIMultiDict instance.""" + + def __init__(self, arg): + if not isinstance(arg, (CIMultiDict, CIMultiDictProxy)): + raise TypeError( + "ctor requires CIMultiDict or CIMultiDictProxy instance" + ", not {}".format(type(arg)) + ) + + self._impl = arg._impl + + def _title(self, key): + return key.title() + + def copy(self): + """Return a copy of itself.""" + return CIMultiDict(self.items()) + + +class MultiDict(_Base, MutableMultiMapping): + """Dictionary with the support for duplicate keys.""" + + def __init__(self, *args, **kwargs): + self._impl = _Impl() + + self._extend(args, kwargs, self.__class__.__name__, self._extend_items) + + if sys.implementation.name != "pypy": + + def __sizeof__(self): + return object.__sizeof__(self) + sys.getsizeof(self._impl) + + def __reduce__(self): + return (self.__class__, (list(self.items()),)) + + def _title(self, key): + return key + + def _key(self, key): + if isinstance(key, str): + return key + else: + raise TypeError( + "MultiDict keys should be either str " "or subclasses of str" + ) + + def add(self, key, value): + identity = self._title(key) + self._impl._items.append((identity, self._key(key), value)) + self._impl.incr_version() + + def copy(self): + """Return a copy of itself.""" + cls = self.__class__ + return cls(self.items()) + + __copy__ = copy + + def extend(self, *args, **kwargs): + """Extend current MultiDict with more values. + + This method must be used instead of update. + """ + self._extend(args, kwargs, "extend", self._extend_items) + + def _extend(self, args, kwargs, name, method): + if len(args) > 1: + raise TypeError( + "{} takes at most 1 positional argument" + " ({} given)".format(name, len(args)) + ) + if args: + arg = args[0] + if isinstance(args[0], (MultiDict, MultiDictProxy)) and not kwargs: + items = arg._impl._items + else: + if hasattr(arg, "items"): + arg = arg.items() + if kwargs: + arg = list(arg) + arg.extend(list(kwargs.items())) + items = [] + for item in arg: + if not len(item) == 2: + raise TypeError( + "{} takes either dict or list of (key, value) " + "tuples".format(name) + ) + items.append((self._title(item[0]), self._key(item[0]), item[1])) + + method(items) + else: + method( + [ + (self._title(key), self._key(key), value) + for key, value in kwargs.items() + ] + ) + + def _extend_items(self, items): + for identity, key, value in items: + self.add(key, value) + + def clear(self): + """Remove all items from MultiDict.""" + self._impl._items.clear() + self._impl.incr_version() + + # Mapping interface # + + def __setitem__(self, key, value): + self._replace(key, value) + + def __delitem__(self, key): + identity = self._title(key) + items = self._impl._items + found = False + for i in range(len(items) - 1, -1, -1): + if items[i][0] == identity: + del items[i] + found = True + if not found: + raise KeyError(key) + else: + self._impl.incr_version() + + def setdefault(self, key, default=None): + """Return value for key, set value to default if key is not present.""" + identity = self._title(key) + for i, k, v in self._impl._items: + if i == identity: + return v + self.add(key, default) + return default + + def popone(self, key, default=_marker): + """Remove specified key and return the corresponding value. + + If key is not found, d is returned if given, otherwise + KeyError is raised. + + """ + identity = self._title(key) + for i in range(len(self._impl._items)): + if self._impl._items[i][0] == identity: + value = self._impl._items[i][2] + del self._impl._items[i] + self._impl.incr_version() + return value + if default is _marker: + raise KeyError(key) + else: + return default + + pop = popone # type: ignore + + def popall(self, key, default=_marker): + """Remove all occurrences of key and return the list of corresponding + values. + + If key is not found, default is returned if given, otherwise + KeyError is raised. + + """ + found = False + identity = self._title(key) + ret = [] + for i in range(len(self._impl._items) - 1, -1, -1): + item = self._impl._items[i] + if item[0] == identity: + ret.append(item[2]) + del self._impl._items[i] + self._impl.incr_version() + found = True + if not found: + if default is _marker: + raise KeyError(key) + else: + return default + else: + ret.reverse() + return ret + + def popitem(self): + """Remove and return an arbitrary (key, value) pair.""" + if self._impl._items: + i = self._impl._items.pop(0) + self._impl.incr_version() + return i[1], i[2] + else: + raise KeyError("empty multidict") + + def update(self, *args, **kwargs): + """Update the dictionary from *other*, overwriting existing keys.""" + self._extend(args, kwargs, "update", self._update_items) + + def _update_items(self, items): + if not items: + return + used_keys = {} + for identity, key, value in items: + start = used_keys.get(identity, 0) + for i in range(start, len(self._impl._items)): + item = self._impl._items[i] + if item[0] == identity: + used_keys[identity] = i + 1 + self._impl._items[i] = (identity, key, value) + break + else: + self._impl._items.append((identity, key, value)) + used_keys[identity] = len(self._impl._items) + + # drop tails + i = 0 + while i < len(self._impl._items): + item = self._impl._items[i] + identity = item[0] + pos = used_keys.get(identity) + if pos is None: + i += 1 + continue + if i >= pos: + del self._impl._items[i] + else: + i += 1 + + self._impl.incr_version() + + def _replace(self, key, value): + key = self._key(key) + identity = self._title(key) + items = self._impl._items + + for i in range(len(items)): + item = items[i] + if item[0] == identity: + items[i] = (identity, key, value) + # i points to last found item + rgt = i + self._impl.incr_version() + break + else: + self._impl._items.append((identity, key, value)) + self._impl.incr_version() + return + + # remove all tail items + i = rgt + 1 + while i < len(items): + item = items[i] + if item[0] == identity: + del items[i] + else: + i += 1 + + +class CIMultiDict(MultiDict): + """Dictionary with the support for duplicate case-insensitive keys.""" + + def _title(self, key): + return key.title() + + +class _Iter: + __slots__ = ("_size", "_iter") + + def __init__(self, size, iterator): + self._size = size + self._iter = iterator + + def __iter__(self): + return self + + def __next__(self): + return next(self._iter) + + def __length_hint__(self): + return self._size + + +class _ViewBase: + def __init__(self, impl): + self._impl = impl + + def __len__(self): + return len(self._impl._items) + + +class _ItemsView(_ViewBase, abc.ItemsView): + def __contains__(self, item): + assert isinstance(item, tuple) or isinstance(item, list) + assert len(item) == 2 + for i, k, v in self._impl._items: + if item[0] == k and item[1] == v: + return True + return False + + def __iter__(self): + return _Iter(len(self), self._iter(self._impl._version)) + + def _iter(self, version): + for i, k, v in self._impl._items: + if version != self._impl._version: + raise RuntimeError("Dictionary changed during iteration") + yield k, v + + def __repr__(self): + lst = [] + for item in self._impl._items: + lst.append("{!r}: {!r}".format(item[1], item[2])) + body = ", ".join(lst) + return "{}({})".format(self.__class__.__name__, body) + + +class _ValuesView(_ViewBase, abc.ValuesView): + def __contains__(self, value): + for item in self._impl._items: + if item[2] == value: + return True + return False + + def __iter__(self): + return _Iter(len(self), self._iter(self._impl._version)) + + def _iter(self, version): + for item in self._impl._items: + if version != self._impl._version: + raise RuntimeError("Dictionary changed during iteration") + yield item[2] + + def __repr__(self): + lst = [] + for item in self._impl._items: + lst.append("{!r}".format(item[2])) + body = ", ".join(lst) + return "{}({})".format(self.__class__.__name__, body) + + +class _KeysView(_ViewBase, abc.KeysView): + def __contains__(self, key): + for item in self._impl._items: + if item[1] == key: + return True + return False + + def __iter__(self): + return _Iter(len(self), self._iter(self._impl._version)) + + def _iter(self, version): + for item in self._impl._items: + if version != self._impl._version: + raise RuntimeError("Dictionary changed during iteration") + yield item[1] + + def __repr__(self): + lst = [] + for item in self._impl._items: + lst.append("{!r}".format(item[1])) + body = ", ".join(lst) + return "{}({})".format(self.__class__.__name__, body) diff --git a/lib/multidict/py.typed b/lib/multidict/py.typed new file mode 100644 index 0000000..dfe8cc0 --- /dev/null +++ b/lib/multidict/py.typed @@ -0,0 +1 @@ +PEP-561 marker. \ No newline at end of file diff --git a/lib/openai-0.27.2.dist-info/INSTALLER b/lib/openai-0.27.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/openai-0.27.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/openai-0.27.2.dist-info/LICENSE b/lib/openai-0.27.2.dist-info/LICENSE new file mode 100644 index 0000000..4f14854 --- /dev/null +++ b/lib/openai-0.27.2.dist-info/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) OpenAI (https://openai.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/openai-0.27.2.dist-info/METADATA b/lib/openai-0.27.2.dist-info/METADATA new file mode 100644 index 0000000..5b47dc3 --- /dev/null +++ b/lib/openai-0.27.2.dist-info/METADATA @@ -0,0 +1,338 @@ +Metadata-Version: 2.1 +Name: openai +Version: 0.27.2 +Summary: Python client library for the OpenAI API +Home-page: https://github.com/openai/openai-python +Author: OpenAI +Author-email: support@openai.com +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.7.1 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: requests (>=2.20) +Requires-Dist: tqdm +Requires-Dist: aiohttp +Requires-Dist: typing-extensions ; python_version < "3.8" +Provides-Extra: datalib +Requires-Dist: numpy ; extra == 'datalib' +Requires-Dist: pandas (>=1.2.3) ; extra == 'datalib' +Requires-Dist: pandas-stubs (>=1.1.0.11) ; extra == 'datalib' +Requires-Dist: openpyxl (>=3.0.7) ; extra == 'datalib' +Provides-Extra: dev +Requires-Dist: black (~=21.6b0) ; extra == 'dev' +Requires-Dist: pytest (==6.*) ; extra == 'dev' +Requires-Dist: pytest-asyncio ; extra == 'dev' +Requires-Dist: pytest-mock ; extra == 'dev' +Provides-Extra: embeddings +Requires-Dist: scikit-learn (>=1.0.2) ; extra == 'embeddings' +Requires-Dist: tenacity (>=8.0.1) ; extra == 'embeddings' +Requires-Dist: matplotlib ; extra == 'embeddings' +Requires-Dist: plotly ; extra == 'embeddings' +Requires-Dist: numpy ; extra == 'embeddings' +Requires-Dist: scipy ; extra == 'embeddings' +Requires-Dist: pandas (>=1.2.3) ; extra == 'embeddings' +Requires-Dist: pandas-stubs (>=1.1.0.11) ; extra == 'embeddings' +Requires-Dist: openpyxl (>=3.0.7) ; extra == 'embeddings' +Provides-Extra: wandb +Requires-Dist: wandb ; extra == 'wandb' +Requires-Dist: numpy ; extra == 'wandb' +Requires-Dist: pandas (>=1.2.3) ; extra == 'wandb' +Requires-Dist: pandas-stubs (>=1.1.0.11) ; extra == 'wandb' +Requires-Dist: openpyxl (>=3.0.7) ; extra == 'wandb' + +# OpenAI Python Library + +The OpenAI Python library provides convenient access to the OpenAI API +from applications written in the Python language. It includes a +pre-defined set of classes for API resources that initialize +themselves dynamically from API responses which makes it compatible +with a wide range of versions of the OpenAI API. + +You can find usage examples for the OpenAI Python library in our [API reference](https://beta.openai.com/docs/api-reference?lang=python) and the [OpenAI Cookbook](https://github.com/openai/openai-cookbook/). + +## Installation + +You don't need this source code unless you want to modify the package. If you just +want to use the package, just run: + +```sh +pip install --upgrade openai +``` + +Install from source with: + +```sh +python setup.py install +``` + +### Optional dependencies + +Install dependencies for [`openai.embeddings_utils`](openai/embeddings_utils.py): + +```sh +pip install openai[embeddings] +``` + +Install support for [Weights & Biases](https://wandb.me/openai-docs): + +``` +pip install openai[wandb] +``` + +Data libraries like `numpy` and `pandas` are not installed by default due to their size. They’re needed for some functionality of this library, but generally not for talking to the API. If you encounter a `MissingDependencyError`, install them with: + +```sh +pip install openai[datalib] +```` + +## Usage + +The library needs to be configured with your account's secret key which is available on the [website](https://platform.openai.com/account/api-keys). Either set it as the `OPENAI_API_KEY` environment variable before using the library: + +```bash +export OPENAI_API_KEY='sk-...' +``` + +Or set `openai.api_key` to its value: + +```python +import openai +openai.api_key = "sk-..." + +# list models +models = openai.Model.list() + +# print the first model's id +print(models.data[0].id) + +# create a completion +completion = openai.Completion.create(model="ada", prompt="Hello world") + +# print the completion +print(completion.choices[0].text) +``` + + +### Params +All endpoints have a `.create` method that supports a `request_timeout` param. This param takes a `Union[float, Tuple[float, float]]` and will raise an `openai.error.TimeoutError` error if the request exceeds that time in seconds (See: https://requests.readthedocs.io/en/latest/user/quickstart/#timeouts). + +### Microsoft Azure Endpoints + +In order to use the library with Microsoft Azure endpoints, you need to set the `api_type`, `api_base` and `api_version` in addition to the `api_key`. The `api_type` must be set to 'azure' and the others correspond to the properties of your endpoint. +In addition, the deployment name must be passed as the engine parameter. + +```python +import openai +openai.api_type = "azure" +openai.api_key = "..." +openai.api_base = "https://example-endpoint.openai.azure.com" +openai.api_version = "2022-12-01" + +# create a completion +completion = openai.Completion.create(engine="deployment-name", prompt="Hello world") + +# print the completion +print(completion.choices[0].text) +``` + +Please note that for the moment, the Microsoft Azure endpoints can only be used for completion, embedding, and fine-tuning operations. +For a detailed example of how to use fine-tuning and other operations using Azure endpoints, please check out the following Jupyter notebooks: +* [Using Azure completions](https://github.com/openai/openai-cookbook/tree/main/examples/azure/completions.ipynb) +* [Using Azure fine-tuning](https://github.com/openai/openai-cookbook/tree/main/examples/azure/finetuning.ipynb) +* [Using Azure embeddings](https://github.com/openai/openai-cookbook/blob/main/examples/azure/embeddings.ipynb) + +### Microsoft Azure Active Directory Authentication + +In order to use Microsoft Active Directory to authenticate to your Azure endpoint, you need to set the `api_type` to "azure_ad" and pass the acquired credential token to `api_key`. The rest of the parameters need to be set as specified in the previous section. + + +```python +from azure.identity import DefaultAzureCredential +import openai + +# Request credential +default_credential = DefaultAzureCredential() +token = default_credential.get_token("https://cognitiveservices.azure.com/.default") + +# Setup parameters +openai.api_type = "azure_ad" +openai.api_key = token.token +openai.api_base = "https://example-endpoint.openai.azure.com/" +openai.api_version = "2022-12-01" + +# ... +``` +### Command-line interface + +This library additionally provides an `openai` command-line utility +which makes it easy to interact with the API from your terminal. Run +`openai api -h` for usage. + +```sh +# list models +openai api models.list + +# create a completion +openai api completions.create -m ada -p "Hello world" + +# create a chat completion +openai api chat_completions.create -m gpt-3.5-turbo -g user "Hello world" + +# generate images via DALL·E API +openai api image.create -p "two dogs playing chess, cartoon" -n 1 +``` + +## Example code + +Examples of how to use this Python library to accomplish various tasks can be found in the [OpenAI Cookbook](https://github.com/openai/openai-cookbook/). It contains code examples for: + +* Classification using fine-tuning +* Clustering +* Code search +* Customizing embeddings +* Question answering from a corpus of documents +* Recommendations +* Visualization of embeddings +* And more + +Prior to July 2022, this OpenAI Python library hosted code examples in its examples folder, but since then all examples have been migrated to the [OpenAI Cookbook](https://github.com/openai/openai-cookbook/). + +### Chat + +Conversational models such as `gpt-3.5-turbo` can be called using the chat completions endpoint. + +```python +import openai +openai.api_key = "sk-..." # supply your API key however you choose + +completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello world!"}]) +print(completion.choices[0].message.content) +``` + +### Embeddings + +In the OpenAI Python library, an embedding represents a text string as a fixed-length vector of floating point numbers. Embeddings are designed to measure the similarity or relevance between text strings. + +To get an embedding for a text string, you can use the embeddings method as follows in Python: + +```python +import openai +openai.api_key = "sk-..." # supply your API key however you choose + +# choose text to embed +text_string = "sample text" + +# choose an embedding +model_id = "text-similarity-davinci-001" + +# compute the embedding of the text +embedding = openai.Embedding.create(input=text_string, model=model_id)['data'][0]['embedding'] +``` + +An example of how to call the embeddings method is shown in this [get embeddings notebook](https://github.com/openai/openai-cookbook/blob/main/examples/Get_embeddings.ipynb). + +Examples of how to use embeddings are shared in the following Jupyter notebooks: + +- [Classification using embeddings](https://github.com/openai/openai-cookbook/blob/main/examples/Classification_using_embeddings.ipynb) +- [Clustering using embeddings](https://github.com/openai/openai-cookbook/blob/main/examples/Clustering.ipynb) +- [Code search using embeddings](https://github.com/openai/openai-cookbook/blob/main/examples/Code_search.ipynb) +- [Semantic text search using embeddings](https://github.com/openai/openai-cookbook/blob/main/examples/Semantic_text_search_using_embeddings.ipynb) +- [User and product embeddings](https://github.com/openai/openai-cookbook/blob/main/examples/User_and_product_embeddings.ipynb) +- [Zero-shot classification using embeddings](https://github.com/openai/openai-cookbook/blob/main/examples/Zero-shot_classification_with_embeddings.ipynb) +- [Recommendation using embeddings](https://github.com/openai/openai-cookbook/blob/main/examples/Recommendation_using_embeddings.ipynb) + +For more information on embeddings and the types of embeddings OpenAI offers, read the [embeddings guide](https://beta.openai.com/docs/guides/embeddings) in the OpenAI documentation. + +### Fine-tuning + +Fine-tuning a model on training data can both improve the results (by giving the model more examples to learn from) and reduce the cost/latency of API calls (chiefly through reducing the need to include training examples in prompts). + +Examples of fine-tuning are shared in the following Jupyter notebooks: + +- [Classification with fine-tuning](https://github.com/openai/openai-cookbook/blob/main/examples/Fine-tuned_classification.ipynb) (a simple notebook that shows the steps required for fine-tuning) +- Fine-tuning a model that answers questions about the 2020 Olympics + - [Step 1: Collecting data](https://github.com/openai/openai-cookbook/blob/main/examples/fine-tuned_qa/olympics-1-collect-data.ipynb) + - [Step 2: Creating a synthetic Q&A dataset](https://github.com/openai/openai-cookbook/blob/main/examples/fine-tuned_qa/olympics-2-create-qa.ipynb) + - [Step 3: Train a fine-tuning model specialized for Q&A](https://github.com/openai/openai-cookbook/blob/main/examples/fine-tuned_qa/olympics-3-train-qa.ipynb) + +Sync your fine-tunes to [Weights & Biases](https://wandb.me/openai-docs) to track experiments, models, and datasets in your central dashboard with: + +```bash +openai wandb sync +``` + +For more information on fine-tuning, read the [fine-tuning guide](https://beta.openai.com/docs/guides/fine-tuning) in the OpenAI documentation. + +### Moderation + +OpenAI provides a Moderation endpoint that can be used to check whether content complies with the OpenAI [content policy](https://platform.openai.com/docs/usage-policies) + +```python +import openai +openai.api_key = "sk-..." # supply your API key however you choose + +moderation_resp = openai.Moderation.create(input="Here is some perfectly innocuous text that follows all OpenAI content policies.") +``` + +See the [moderation guide](https://platform.openai.com/docs/guides/moderation) for more details. + +## Image generation (DALL·E) + +```python +import openai +openai.api_key = "sk-..." # supply your API key however you choose + +image_resp = openai.Image.create(prompt="two dogs playing chess, oil painting", n=4, size="512x512") + +``` + +## Audio transcription (Whisper) +```python +import openai +openai.api_key = "sk-..." # supply your API key however you choose +f = open("path/to/file.mp3", "rb") +transcript = openai.Audio.transcribe("whisper-1", f) + +``` + +## Async API + +Async support is available in the API by prepending `a` to a network-bound method: + +```python +import openai +openai.api_key = "sk-..." # supply your API key however you choose + +async def create_completion(): + completion_resp = await openai.Completion.acreate(prompt="This is a test", model="davinci") + +``` + +To make async requests more efficient, you can pass in your own +``aiohttp.ClientSession``, but you must manually close the client session at the end +of your program/event loop: + +```python +import openai +from aiohttp import ClientSession + +openai.aiosession.set(ClientSession()) +# At the end of your program, close the http session +await openai.aiosession.get().close() +``` + +See the [usage guide](https://platform.openai.com/docs/guides/images) for more details. + +## Requirements + +- Python 3.7.1+ + +In general, we want to support the versions of Python that our +customers are using. If you run into problems with any version +issues, please let us know at on our [support page](https://help.openai.com/en/). + +## Credit + +This library is forked from the [Stripe Python Library](https://github.com/stripe/stripe-python). diff --git a/lib/openai-0.27.2.dist-info/RECORD b/lib/openai-0.27.2.dist-info/RECORD new file mode 100644 index 0000000..7f1ab53 --- /dev/null +++ b/lib/openai-0.27.2.dist-info/RECORD @@ -0,0 +1,111 @@ +../../Scripts/openai.exe,sha256=GIhD3jWw5PMYJ1QTSERowaEis0-tpWS3s_w_hUzLwRU,106369 +openai-0.27.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +openai-0.27.2.dist-info/LICENSE,sha256=vLo94hSFHM5G7Vr0LWaYBEYW7qzoh8MjG8eiBHSrY54,1083 +openai-0.27.2.dist-info/METADATA,sha256=LWCRBj1UaVCzX1lq2yHsOF4sDHPTGL718vRDIC_TYt8,13127 +openai-0.27.2.dist-info/RECORD,, +openai-0.27.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +openai-0.27.2.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 +openai-0.27.2.dist-info/entry_points.txt,sha256=q68fD1MsMkiejaG68n2mKmbuZEwWkq89oniOBBq9fo0,55 +openai-0.27.2.dist-info/top_level.txt,sha256=yroxRDiE4kOdI0tEpF1d7ffoQJMxe4i3z_pKoyou9wg,7 +openai-0.27.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +openai/__init__.py,sha256=NKBGPoh7HP3XXo2VgSfqkovavDHOPjC6G0GPGBvY3mo,2203 +openai/__pycache__/__init__.cpython-39.pyc,, +openai/__pycache__/_openai_scripts.cpython-39.pyc,, +openai/__pycache__/api_requestor.cpython-39.pyc,, +openai/__pycache__/cli.cpython-39.pyc,, +openai/__pycache__/datalib.cpython-39.pyc,, +openai/__pycache__/embeddings_utils.cpython-39.pyc,, +openai/__pycache__/error.cpython-39.pyc,, +openai/__pycache__/object_classes.cpython-39.pyc,, +openai/__pycache__/openai_object.cpython-39.pyc,, +openai/__pycache__/openai_response.cpython-39.pyc,, +openai/__pycache__/upload_progress.cpython-39.pyc,, +openai/__pycache__/util.cpython-39.pyc,, +openai/__pycache__/validators.cpython-39.pyc,, +openai/__pycache__/version.cpython-39.pyc,, +openai/__pycache__/wandb_logger.cpython-39.pyc,, +openai/_openai_scripts.py,sha256=-SYV_h-4pTfyG4IB4YLUcejX7QrHZGSvS8djkbprR34,2047 +openai/api_requestor.py,sha256=l0TU4WiMN1ffE3WMZPOC2bR4YTF8V-HsToMrSvhDyZo,22828 +openai/api_resources/__init__.py,sha256=S5vOHXDRZu1kHikYR82kF5QUU4jEK7rMr2vEaoHQn20,907 +openai/api_resources/__pycache__/__init__.cpython-39.pyc,, +openai/api_resources/__pycache__/audio.cpython-39.pyc,, +openai/api_resources/__pycache__/chat_completion.cpython-39.pyc,, +openai/api_resources/__pycache__/completion.cpython-39.pyc,, +openai/api_resources/__pycache__/customer.cpython-39.pyc,, +openai/api_resources/__pycache__/deployment.cpython-39.pyc,, +openai/api_resources/__pycache__/edit.cpython-39.pyc,, +openai/api_resources/__pycache__/embedding.cpython-39.pyc,, +openai/api_resources/__pycache__/engine.cpython-39.pyc,, +openai/api_resources/__pycache__/error_object.cpython-39.pyc,, +openai/api_resources/__pycache__/file.cpython-39.pyc,, +openai/api_resources/__pycache__/fine_tune.cpython-39.pyc,, +openai/api_resources/__pycache__/image.cpython-39.pyc,, +openai/api_resources/__pycache__/model.cpython-39.pyc,, +openai/api_resources/__pycache__/moderation.cpython-39.pyc,, +openai/api_resources/abstract/__init__.py,sha256=ZUZv2MkEpRUeRZ0NtRHczXNAamFTUK17z87O3WpXzLQ,540 +openai/api_resources/abstract/__pycache__/__init__.cpython-39.pyc,, +openai/api_resources/abstract/__pycache__/api_resource.cpython-39.pyc,, +openai/api_resources/abstract/__pycache__/createable_api_resource.cpython-39.pyc,, +openai/api_resources/abstract/__pycache__/deletable_api_resource.cpython-39.pyc,, +openai/api_resources/abstract/__pycache__/engine_api_resource.cpython-39.pyc,, +openai/api_resources/abstract/__pycache__/listable_api_resource.cpython-39.pyc,, +openai/api_resources/abstract/__pycache__/nested_resource_class_methods.cpython-39.pyc,, +openai/api_resources/abstract/__pycache__/updateable_api_resource.cpython-39.pyc,, +openai/api_resources/abstract/api_resource.py,sha256=wzCww3boKgr0Ru33CIRi8lOCsMEtQJLKlz1HMHIpMkY,5443 +openai/api_resources/abstract/createable_api_resource.py,sha256=w6atKaf0aUDe3NuV-wLtbmCxCGPtCxXIkH98yk8s7xQ,2573 +openai/api_resources/abstract/deletable_api_resource.py,sha256=9jI4tt7RejbO0aIyLJwivyg3jclr7r55-l50mVFC7GI,1624 +openai/api_resources/abstract/engine_api_resource.py,sha256=GGj9NpJkPqKozgTT3E2meIS1VsRq8YnTg_A3Uzh-P00,10007 +openai/api_resources/abstract/listable_api_resource.py,sha256=-okp69Lu1OaA9aIkEK2nC-JXRW44vtCKFCN89w_4f60,2678 +openai/api_resources/abstract/nested_resource_class_methods.py,sha256=7rCNmuZhLgbN7M6PcNT-P9W5taHFZF-ObDYXz7GLFuI,5086 +openai/api_resources/abstract/updateable_api_resource.py,sha256=cQQd04fI85DTYpOqM_LFuZ9ldDKrkT-MRetkDG5FUU0,534 +openai/api_resources/audio.py,sha256=0ln2wO0IEfQoCoeVi10BSaig1X8k_kTzrHEG7immm8w,5690 +openai/api_resources/chat_completion.py,sha256=8mQ-UPp9FcJTMq2ae9nsTpLHnjEjSR7T788l-S6jQIQ,1587 +openai/api_resources/completion.py,sha256=EDgsMEJXwYyTqB5fGy2Vi6nQeproKZWvNmkdNve5Ykc,1610 +openai/api_resources/customer.py,sha256=vX1reXMqDHLssmT2aGev83TIE4NbnzvQ73yrQWyDpFk,539 +openai/api_resources/deployment.py,sha256=9K64JZyWxVcvutebf0qguYrSRPb0V_lMALwNsWRqNzs,4055 +openai/api_resources/edit.py,sha256=HlE11SFBBJH_ompIBHAYToNc4tIdUu01tZyym7f8LlE,1963 +openai/api_resources/embedding.py,sha256=wbmwNYb6V7_tWJYd1h487YwltIZ1xl2eZhITInqNP-k,3387 +openai/api_resources/engine.py,sha256=_bzICgcNFmpdm08yL_W3n-vekkO0AGcCdPGl352HMT8,1665 +openai/api_resources/error_object.py,sha256=gKtXIo8mYZDPE2GiJUzQ8LDzLKKEPLqQoyYo_AAqOA4,892 +openai/api_resources/experimental/__init__.py,sha256=fLUruA1uO11wqsDNuAKxgx3qVhQyPYC79JoZNTCIq1A,104 +openai/api_resources/experimental/__pycache__/__init__.cpython-39.pyc,, +openai/api_resources/experimental/__pycache__/completion_config.cpython-39.pyc,, +openai/api_resources/experimental/completion_config.py,sha256=7vlxmFIkpleNCEUir50NBk31XxnqL0yBFZ5Ah1h9TpQ,274 +openai/api_resources/file.py,sha256=TX9xFoWeAtRAGyHUTJhcI3BnpQtZKHlAW35mT6fPrGQ,7737 +openai/api_resources/fine_tune.py,sha256=uotwurKyV8wAnyw4x01O2b2t3b--8i_EBSBVbV-2EhE,5263 +openai/api_resources/image.py,sha256=-2Ta2Kbb5zk8wR6pOcMUQnCQiwTZlu7BlvHYWSRsgWk,6267 +openai/api_resources/model.py,sha256=BiXo7j4P6-rhZW8my4eAbD6aIdQ6sAf2Dw9e-U21b_Q,169 +openai/api_resources/moderation.py,sha256=XLEVJbP2A5AqcnelbIcXq7-PMqvfPPibOetOf2xiMTA,1376 +openai/cli.py,sha256=ydhHJk-eUdD-4-xltN2iKmH5Us06ushLWxadexK1Mv8,37805 +openai/datalib.py,sha256=Wec28jxx8bzpkmL46NWfigjwnk4SJqOe_3zrIJmMe2U,1362 +openai/embeddings_utils.py,sha256=wTC17PYHMcMnUdBwUnMkIt-YIOwRWdysHrsIYzafELk,8659 +openai/error.py,sha256=ou0Smzmx-IeZN38oDc4fmNp1dS7QPx4DpbDws65tGUw,4220 +openai/object_classes.py,sha256=yN2tqUdG1ATov5C1zt06cG1o_ad0o5qYfZlK4bso7eY,379 +openai/openai_object.py,sha256=cw_BUglIo8F-U-vKpcEhB_X-wBvdAN4cjdlNYzxkOM0,10790 +openai/openai_response.py,sha256=LQE9Hm81VsGL6UFYw9rGzlFONPNJx-46QjNVtlp_3DM,536 +openai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +openai/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +openai/tests/__pycache__/__init__.cpython-39.pyc,, +openai/tests/__pycache__/test_api_requestor.cpython-39.pyc,, +openai/tests/__pycache__/test_endpoints.cpython-39.pyc,, +openai/tests/__pycache__/test_exceptions.cpython-39.pyc,, +openai/tests/__pycache__/test_file_cli.cpython-39.pyc,, +openai/tests/__pycache__/test_long_examples_validator.cpython-39.pyc,, +openai/tests/__pycache__/test_url_composition.cpython-39.pyc,, +openai/tests/__pycache__/test_util.cpython-39.pyc,, +openai/tests/asyncio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +openai/tests/asyncio/__pycache__/__init__.cpython-39.pyc,, +openai/tests/asyncio/__pycache__/test_endpoints.cpython-39.pyc,, +openai/tests/asyncio/test_endpoints.py,sha256=ko3ra_FcwlmkCzxEoVwIyOTYdSNQn7VgSqUGQ7O8kro,2362 +openai/tests/test_api_requestor.py,sha256=HbMEbeaM9MA6lhmZ6dQQ2GnHS-OnqNzSn04MnFqnJY0,2354 +openai/tests/test_endpoints.py,sha256=iLoJimkK6gI-AoGkb9UZ0sRN7yiqWycJXdnLlwvZbFo,2209 +openai/tests/test_exceptions.py,sha256=VbKHyi7QAoLi_t6IMuJWSdSkY4zJD4ngrNRjQ89GIjQ,1156 +openai/tests/test_file_cli.py,sha256=BIBCaDJ6SZsAtvNqERhEQLDutJjoejgV9GCP3tYcu1A,1418 +openai/tests/test_long_examples_validator.py,sha256=fiibNigP1YFlOZvs7NdmDErC1ZFuaZQEjku6SDLMUt4,1988 +openai/tests/test_url_composition.py,sha256=d3Jr-bmCFLedEYu3FTYN180WT6wBygRABW8g9R3ccsA,6506 +openai/tests/test_util.py,sha256=sZYMnEMG1_1Y7feqr8e-_dcRqvQrgTNROWuusG62x4Q,797 +openai/upload_progress.py,sha256=L723nchd5OQ_tfDFDKOru_rlHdG73uzesOh528wOOcM,1188 +openai/util.py,sha256=_kb73f0Hb6J83I7fPLQUGKxz84CcSMVPS1kD2rAwYMY,5403 +openai/validators.py,sha256=GiN8mmG9-v5RdBdZRSB-0PUMGfcozKEFZ6Ft5IDfAEc,33767 +openai/version.py,sha256=PWvgg1pO5onx_obdvWT0QYlTRqg_tTpSoXUCMryf9-4,19 +openai/wandb_logger.py,sha256=PZxSK9w8EnyYFKEgBUUVngnxbsBOJllp3e3qncGxUFY,10361 diff --git a/lib/openai-0.27.2.dist-info/REQUESTED b/lib/openai-0.27.2.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lib/openai-0.27.2.dist-info/WHEEL b/lib/openai-0.27.2.dist-info/WHEEL new file mode 100644 index 0000000..57e3d84 --- /dev/null +++ b/lib/openai-0.27.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lib/openai-0.27.2.dist-info/entry_points.txt b/lib/openai-0.27.2.dist-info/entry_points.txt new file mode 100644 index 0000000..7885109 --- /dev/null +++ b/lib/openai-0.27.2.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +openai = openai._openai_scripts:main diff --git a/lib/openai-0.27.2.dist-info/top_level.txt b/lib/openai-0.27.2.dist-info/top_level.txt new file mode 100644 index 0000000..ec838c5 --- /dev/null +++ b/lib/openai-0.27.2.dist-info/top_level.txt @@ -0,0 +1 @@ +openai diff --git a/lib/openai-0.27.2.dist-info/zip-safe b/lib/openai-0.27.2.dist-info/zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/openai-0.27.2.dist-info/zip-safe @@ -0,0 +1 @@ + diff --git a/lib/openai/__init__.py b/lib/openai/__init__.py new file mode 100644 index 0000000..2b18422 --- /dev/null +++ b/lib/openai/__init__.py @@ -0,0 +1,86 @@ +# OpenAI Python bindings. +# +# Originally forked from the MIT-licensed Stripe Python bindings. + +import os +from contextvars import ContextVar +from typing import Optional, TYPE_CHECKING + +from openai.api_resources import ( + Audio, + ChatCompletion, + Completion, + Customer, + Edit, + Deployment, + Embedding, + Engine, + ErrorObject, + File, + FineTune, + Image, + Model, + Moderation, +) +from openai.error import APIError, InvalidRequestError, OpenAIError + +if TYPE_CHECKING: + from aiohttp import ClientSession + +api_key = os.environ.get("OPENAI_API_KEY") +# Path of a file with an API key, whose contents can change. Supercedes +# `api_key` if set. The main use case is volume-mounted Kubernetes secrets, +# which are updated automatically. +api_key_path: Optional[str] = os.environ.get("OPENAI_API_KEY_PATH") + +organization = os.environ.get("OPENAI_ORGANIZATION") +api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1") +api_type = os.environ.get("OPENAI_API_TYPE", "open_ai") +api_version = ( + "2022-12-01" if api_type in ("azure", "azure_ad", "azuread") else None +) +verify_ssl_certs = True # No effect. Certificates are always verified. +proxy = None +app_info = None +enable_telemetry = False # Ignored; the telemetry feature was removed. +ca_bundle_path = None # No longer used, feature was removed +debug = False +log = None # Set to either 'debug' or 'info', controls console logging + +aiosession: ContextVar[Optional["ClientSession"]] = ContextVar( + "aiohttp-session", default=None +) # Acts as a global aiohttp ClientSession that reuses connections. +# This is user-supplied; otherwise, a session is remade for each request. + +__all__ = [ + "APIError", + "Audio", + "ChatCompletion", + "Completion", + "Customer", + "Edit", + "Image", + "Deployment", + "Embedding", + "Engine", + "ErrorObject", + "File", + "FineTune", + "InvalidRequestError", + "Model", + "Moderation", + "OpenAIError", + "api_base", + "api_key", + "api_type", + "api_key_path", + "api_version", + "app_info", + "ca_bundle_path", + "debug", + "enable_telemetry", + "log", + "organization", + "proxy", + "verify_ssl_certs", +] diff --git a/lib/openai/__pycache__/__init__.cpython-39.pyc b/lib/openai/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76e8d5e78d0e9274a507a7cab660c8ba91e9b2db GIT binary patch literal 1502 zcmaKsOK%%D5XW~{yOylCE%_lQaUM?EDy^T#z$l_Bav~(PjMymilvy|I|Z?9d`;ozNBzacpd7!i;c3NvL9@6XWq_1dy~8TZo}gD*y7=*m^}FJ&tnR#_`x9$=DiXtLvM?3vC71f zf3j-iFq=GNOXLyQFW~)>Et9XnHo-my`!#o1joGaJvqQe&4tLo~!%DxmdT5O-w)UG% zwwZluHP`<}6_1;?$+zQ3^Y{7(Dw~eU^%5P#5e-f0?CnX%Z@=iYUv`h5H_OIrjad+z za{Genb}W*RVgh#De1njWX4g<*Glq&_(Nx65@Ic{}*Si^tWc}r@a_x+RB*$Y#oN#I}dsh?1N zaUIfo{k(PDC9Sh=@0e6>%=@l&+PP|6Xq~7hySp%VCx#SgurrLs?qvV7Sk#qdaondt zGebOjHI|&@QsYxbvT2L5tNg*$%+0j*n1~GnNTg^5CJIgYDPRF5^t58gn zhk#FGo3aKj=^+*nSp?i5OD-a+2)tUdfG8qLh%%yrs3VpT%ZL?#$)PL*s<_F{1)90^ zG~sd$BMRuBaEVgmSmFoKIh>jy^#|jKg*;W%c#IFmBU1&D2H?{?hg@pa+%Zn9Okp@Xz!NH!bTEenNC(cD%wXR54?*hl za$s_jt9UGjT>ZB?|1V6c-L){JIA5FNVV=pJN{50$EoViWRVd4&NgzR(%m_An3)4$Q z%P1{P#rJ6x#X8-z@_o65;~R+Eh&zZ!h=+(x#C^nF#683VL<3;5uvi%S{_mFDK_}v$ zSM4YLHwtRgKOM>-=|AJ@QpZVulxSaFP`F@y4VUPk|0)>tRiOEHLWh@d3srx%$6X() z8ED_%Nv0-uC}K7a`L}q;)E*#j7wxL^v|4vuyXX|%b*E^T?Xpw;=ww{`#?NtV_v5Pf EFNkWL?*IS* literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/_openai_scripts.cpython-39.pyc b/lib/openai/__pycache__/_openai_scripts.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3245981dddf0bd3ce63e01107b89d6ea666abfd9 GIT binary patch literal 2034 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 b?-_sB`2FLb%zQF4K0Z;}Q{vqxGw=JqGymll literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/datalib.cpython-39.pyc b/lib/openai/__pycache__/datalib.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73661bd86d9a642bb0d329886222c83769956bef GIT binary patch literal 1706 zcmb7EQEwYX5Z*gG@%dsm0i;U2?E^wsq@e|ghX|pXqyj_ZMs=d9htYX+JN7NP+q>@W zC3O%2k-qT24?yA%^npL*M|k2d@Wkw%lUOYh;_RE9-I<&H=9}4N-EK(m8T|e>f4oM> zpLVkP1UR{mul@}MClR^maF6?$7kN*Jyxk<=&)@Zk{c%6?o)YQbz4B>9jccDTuYDS| z4>ZBM^rAH!8#u1>M%04Vw*hbRwO_ob?eul0ho2MP;=v2T1K7B1L>=Dd;R_OV`3CRc zc#HcZ((6_~`-Fr?sn9epc$EQ7AuDmhPJwb}7|q0qVoHEU4c8{J%FFqLGRf(rM9K7o z7Nrq|WEtg9g5)3*^xp|FF06ddIHy%fO+oK{bob!1_qXYJDw5RgrKvD9Nm-VGoC!Iz zUKlJZ=9HCXCK6_M&|#&W(c-Ov&8d(SLS-9_0mS z`NxmH8ae|B3fY=d1UM<#kC=5Dks@W7bF6x3}y!;y34n4x~SlcC#^AzN(CAUTi zz=2zuEu171$TCI1DUy<*!KA9FW@*SY^5?2lvmh+*Rbs>$P*4g<%0X*3bDc2)pe)k` zooFysxq~f*H2sC=g$yr#2*YL8?z>$Yh7_M%XjlEocgxa$KS7J@-6=3rA=V`sSR>A{ zIIs2OZXM|zT8a`g(#Um&gn(q?YuoJ@_?s|Xthpxdc~NV*(_+1D^~3^PeLS4p$5(ey z0Es+2?mqYMX!(oV2Q}dRt3xq=U}+fk8jB!~CCed>7hxRN2M_g596zgAR$E>YW$zIu z{{CujcYKWW>hUO1q8#soJ~c%-o|PunDdV8V2DJHb{7{^XwJ>m}WXY-JdR%As!7@7} zS~W539g7){J_$TGe=RX<+p|tvC=BLjJmX(_KVgC+^3tV3i>dAjYdSeArUFnZ8%+>iXu}RsH$5i zqRuK8i*>vxa%Plm6}4&o+Yg>6;O_d56s{i`wX O0>9b8wZ>L6So;SimffBJ literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/embeddings_utils.cpython-39.pyc b/lib/openai/__pycache__/embeddings_utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95ebce691a911ac9d4d0dea966a582804532a531 GIT binary patch literal 8734 zcmb_hU5p&Zah{%;o&CAJ|Kt6T5;gpjxT5ZOB=u+dL`orTNshocq!Ji|Q&zLvvwL$l zJG<(cCGS?ViDOb0li(6NFr3(?le@rq;6wbFC%**<5WoQfUkUin6SG!;KMZM3yt&Xk*M8lV!sj zZ%kMdjY(@#erLU@#v$uaW7?W-%vdwBj(M}zEauI5bB)8+5m}c<`5Eh3rZ|V38D8K+ zytt`4(|q_g<0G3Yf7LnaXwGwU3Lm?t@^LEZ7=dOXnT>rK_fXjFt9GH ztY6}1G20?P2dp#v(vz^jJ-T1w7w%>G%ls8!&vSM|DP7(ndAe3oWBolh2;rS~PoT?k3Lpi86g%eK?D$rtkUb)&9 zH=U9eXM_`qjd&smo2{~43ms9m!_aB8!g%JE?S^Gx`@GpGJL|2c@A&Dw8Nc0VZD3}j zg=Rb0VW4(r3IJ4S4c7;-*LRGhLZ^XZou+trBy`rp`M_d8bivICuSeNsqI_X?b?liH&B<@ohKG zf2r3R8&%=hp>tmq)95Fj1-P$>Q+Pdm|H{SX-v}HLEZ?XKx3zrL3D&Uc<$5bD2P-yr z#B%5a;rZqF+{$v`hR%G;uC8Ing5_kMvwb3hayxXrV4<}U57(Wr+;14iL;-O^(Tl2h z0q+C(^f!dI2=FvMy$dV!j?|sFtRM0cs`=SR)FE?vtk^wP;9(xw1p`tGU*YOczX)s!B zjRjL*fpNXfzJbl>`W@6?Maw`eAQYKRbye@GVJ1?!!LxT@6gSnb%JULmC48t|;o6P@ zx*smQzv7q`JFKplf!lG+V5Qmixal`Tv*MVL+`1#oaK-k`^NTOPzEC9r>RlwLB=|S* z1jNfa==or*3(t~a5KC3ep;-{O?lZ}62&lTSn8?*W78jO?J=JETb!h<| z{sWKFIlhPO!NQW)gmwp)Q0O6xBS73kAQ4MMN&;z@A`m6@M7>7(!JT!vwf9-flG3i7 z&MkR%qr&Y==g?7H0O*)cCL%$_K-!wKv4@DBI!hwRa7Zo{iAx-YH02Nz&(So*3UG_2 zj2Ta;k?bMi6XyZwk<^roSo0izAh_ZLH5~A_h)>yUmEP?>!`cjpZnNT=1xRQd;!Rhae>UlQw2+Wk>-%o%9YB} zfC`ePHwl9{P%^|NDjy?ooWN-UBvC?=`X-h3ju4OTqx==%?2@5blA&Kg-5wbtoyg$1 zLdaxhLuHFaN(74$Dy!-i4!CWWme@9%QjTDiC3X$xmFl-*jW6D2gk6SlIB;KcpI=vEbLu$%#oeY@B}qrh00yBuR0yOpDLfSno0-p zom{9z`fWzGlTj?4SW+m)nePDI-p%G;`?oZSxUsLDE6z%%DtuAYk5#^VZ zC`+(g2(wSek8*_f(U)o5t=u-oBS_D7nad7|yZQ9jPnbQ9NVO$W`To)$VvWvNg&_xdta>A{vh-rrq)Tpa|T> zY-9Y{O^s;T*Z=6&&|O91fvn#krsP#|>kv8Eb%_Ox zX?&gNf6efd3DsM({8U<+i?kmo{P`dDdK`{Mqp@wQnP(u`FWARm-wGzKul5|;`Rmeu zHCtXY4B@MpwWcs_v(ffKx9ZtJV75fF;yH~4^W9Kl1!n3Mqb2o<=MyLRC9{QNyw@vj zGm|zGel3bMM>u}fF>B|{YSRy!Z4oRKBt=)69Daf4)>lGr!)!FUTXP-$l38ho=86NS z7PS>SaJbp@&6O~01sBhr4XW;%8_s)3n2>O?prQ7Rn0PmyW zRz(xv*>ul3ubA`ZTS-(i*gsQ7HD(j_CW4t{3!UJi`SIe##f9^=Zb{vt7<`9LZ5plS z*2#~nfB$=Q{oUoXuyc8b>>T144FOI4(>ANr*!?>76CmhvtD&G1MPvaiG~aZ*IMWiYAI4e`ir64wLr<;cAy~uq zwS^<-kg{?(;1B>;9ZM&HvvRf%w;b_UY}hTtYTVN5)tWULv@6selx@D+4nj*`_uPi1 zZxA%rJ-gy~ma!q7cEhpqepyyw zTa83f;(Xu2SO{S$&bG$W4IXHNcn8D9tZa?;vCC5ItzyzA{W>6+PtbC;gt#EifEVDW zAy*MV%u}jglQV3@%(FD`E>ZP&X(=*VlV-8}6is~_sKF|LlFKoj%|6Oy3`Td6Ewa;^ z%8HK)+6Yroqa(VbThA5Y)9AQ!z?xzw*-5p)3Mdt*?vbt+*@4P`cc~v8-u7fF7yN7Ac+mh8|hu7#bouQheAFiyd?N05^56a5@tBNr~e)! zpXF1vrg4>PcXE;RSh`xI;mpxH?_8J32ZVQy$zcX<*#7`wUwF%Ld>l2BFtbmMJSc3F z45rzv4eEmh#L#OX#Zuophv&S7OpafBvazNxPzFI0T}H-wPPwZ_?Dmt)wTz7JgHb&i z%ME05+^RLUuObj0Mowg)Z??aODw#wuRP|A3zSkuv*90Yddby4Ry?r>u#BVFt>y<1s zUqw2nf&g^Q$~*0<=W@sPt$cOGscZx*&P}jG64D@QngDUWH$ae3=TnQ6(7Argeu6fE z6HEXo*+e9bpBu)As(xnZ;v>|;NXpE??!bu3BpFxmNGCk;tG6lQXPvjx{T}QgLSUEn z9NX#Rq(;I9ZyfZMrU*=85Tu~D-+e+CVAg2@99^PLfhH(v;d^k-CQCb$j!ub=bacGa z(UaBo$J`=| zJSowL0fT|}v*{=hcTKZj4E_>>1qOhkll>6uDD8=*h$vzR8Svm50B2abvaQ@?Vv4KC z0#t`}%o! z7wfL?hT51$)8b`+)cw4{nVJX^;&m@R)V z6B)F>t2%Om8H)D!5L6`})!{g$v>y)}Ibw1d|4Q>v#ml`?`n7O_Hr$N-%ijY`vt3oGQ~mnlhqAHO-s zhsWD!*ka8QIETJQ$oZ>i{<45i2B&k~$_8*bv19RYyM>MpSt3|-u?*Z&LHsUFLh>rU zL7lVfW$FCIaQTu#o<>J%q~nN)FyMH!!VMV_C27Qr+<|>7iVq#QFF(Nu0i~0a!U)sV zF?c#f_)Y4gF;#sCuc;vYClxi)4=O&?v+4sQm-{axFa8X@9u^Zd4Dsv5JYUN0BVW)t zF1`;CXFCb~;s^MuW&wH^soyXjse*rv!X6dOeI6BzGJTaaq)KL?R_{rb%<&vlvVw%k zZk5cr8K`7ls$@P@Nf=e}b)HR?^7qq{D!$8esZtj18Bw;clm;mz{!X%PAu2#Aho7XB z7yf^hQrrfQlI{JZUphr4vZ_)Y|CI_-9bYY{o#@V%CxRCcZ_j-zMxa1aWaMj-odjU$$&Xw-8n_@O7`RwH?f52<}bHf}87FDu+lL~>~%VSEN@ zqt-^^bRP=VlI#(8r1i&7M5KtM4`z+^+8Va+){tqCJ_JJHv`Z2o1TyHB;WrWP+vK+k zN|y*atOc11ne4Vkl7Es5WPU-pC{oK60%T64@fE*?Qkw`DR} z#&*&>x3c(KOg>`b&XU(Ay$xyWVhty)+^$`+(U;;lw}iY{+jB0FJ0Fl(=Gd58ggs5} zF*c=AOj*>X*=%lFRUVO5H9i|DCS`*aJ{uX{UC!ybca>jI6kAY@pBE>`4C;e;^|NB3 eUm8+GMe#(xjwuh)TC(bNKlpF0^n8{8 literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/error.cpython-39.pyc b/lib/openai/__pycache__/error.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1706db7afe5a67c5cbd56bc2a8bc3f8ab7f28b33 GIT binary patch literal 4794 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 AtN;K2 literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/object_classes.cpython-39.pyc b/lib/openai/__pycache__/object_classes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dac57800b82e88e2f258c0ae49829892ac9fe68d GIT binary patch literal 488 zcmaJ;%Sr<=6iqtQ>4R3>x^dq{E9gc7yi&} zUHJ=Ld8dV<8*ey=+;A@EB#GPYCc(P-JV@UtAzx+lyK2~6BFrgLN>VCFMj55$z9K4_ z!yHP0&RlSis|XKKL)25}-D8c^4M?Z@jnkw5MC8%oJLRM|*rY1lA-b~q5M7_plvVGxrv05M+k3da9r#a?Ke`Hn zLGVFz`HUM{Kl*3kvjJ_wAkkcfeK2z?RX9~PGB0roI<#QyaX8}R&~OV~6({p}3MSk} oKa%4aBsNM!Yz&y5S{C(ly!b-E1^ys0`$#R?a$H)W?slzz0Ku-3EdT%j literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/openai_object.cpython-39.pyc b/lib/openai/__pycache__/openai_object.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56b3f4b4567fc6ee28c111cbdf7b0b078ea2c7ef GIT binary patch literal 7667 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 T2*_o3l|UR{#2x)So@L{oP{wzK literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/openai_response.cpython-39.pyc b/lib/openai/__pycache__/openai_response.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f8aa4cf441785327e0a5047a20a32987b4a4666 GIT binary patch literal 1149 zcmb7DO>Yx15VgIVbepDph)T2|apICgio_8iR8%BX2~tD_hiE0s#BOSu>~6=-p;0)s zKZ64td*BcG%89?gi5YJ~(})X>Jee7f?f0H%)#hf4;W+rQqqhZPKPgxq2?l#O-6J%b zX+C0O-shaXW?JavlIi3^^o353Sf}v|YgmVe?6C5-EYxT~iRF=Eu!qxqL}OT=YlfS- z7MHA_nA9{(a|e(7%5|z6m+1xXuieNt^;*nkF}psm&GdTAwsZ?&Y=m2fl__2xyf>~Y z3ugvIVll`^qHG_hyMtyXW=ygfM~i51Kks!!$mEGp+JFnGR$g_IkUBFuh6e5kYxA`$ z1{ub`kM^JC9~>6vM+2}`{>r#hUsn0B^3t8qF}XL+Kh59RPr0++bSpJDRYT+Q5|JwV zr2dmIN4#5|BLZm)>!qYC9SO&HlTYv8tr`q`-pD0JVZFFYrGyBFh z|IU%B=uKN-&L#-1YxR`?x+Ww;Kx{+e zJiu-AfSNxfw(zh;;kCHYqvr+Eh5$K|KjT>{_*PQSxYon(qUp~ouIQ$op?Oi9g;WK&@8DIa@Zv#!L6rvg=b$(p_j)eP1^ z@hukB-@pcIq$H<@z-ArOu0UwkBTOzPH-g)>djdz1El7?v+KawbLtOyPGI&TBWw-!`9cG?UwD7 z`jcLg?j<|gwu+w4lg_98URES3?wMl8Xlqw{2PS}JAJdX2#sfaMbuqft@taakH_iv? zG+mfvW5~J)%SfmZA3d9!8g}mOBQb+*Rf}U=nmDG7b;$9-@)eivN&{E}l04bhaa`5o zc)w8njP$uUe$h{|(Jw9rGfT#C+vpywPkANm40$u-+*o>W$U`rb?(7O7D2jDV`w_&1 z5Oqfd4odhcAWEoNM=uUy2oPhgn%n5N+uD4mDH(s^Tab-xjR)xya(s?W2}&FhL3k;; z^2D1^gwFgjIR9NJ@f!4rYebz$HVi=@@yb)$rcqeQOy`vslN6nzs%;#A2v5Itz*IAn zd{^N(^EgbCEZYV@BI(jp$?bmIEW+*-bPhEFYXQ>|kc`*)Jh5bHI@JHgk(;n)sQ52A zB!(c8IHu|uGvwwackaTQe=sgoAAw6IH|f(HqGa7kr054ckmH?Z7EL-30g#o1Ydx5pAT+az>b_o!rZ{c1}gO))+jJ@ zSj;sN^CT$e=4}#Wddl5@#YC>IW1bgNJwP$h*_C`yL_~c1F$B6Q%NQ~ty6hMocF2`{ zEq`OL*el)^u8W6Wb#$i`qF(^wyX=Vm?nI+Qm5V+hJ9&wPccwDwVi4bKd`4~vIK~ZS pD5{MpIw7JXt4pnWfV(thXTpdod_3yUYt)_G%R{*^ySNly{}(GUiZB2G literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/util.cpython-39.pyc b/lib/openai/__pycache__/util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..927e43821c663876436563ca21f280a6ffe7553f GIT binary patch literal 5416 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 literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/validators.cpython-39.pyc b/lib/openai/__pycache__/validators.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d028f567f891368900c6cd857c2860d608644a9a GIT binary patch literal 27947 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 Al>h($ literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/version.cpython-39.pyc b/lib/openai/__pycache__/version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d25e5f1979ba14bb3797e8abcd64156864563ec GIT binary patch literal 184 zcmYe~<>g`k0?)OQDZ)VdF^Gc<7=auIATH(r5-AK(3@MDk44O<;YzBHp=6XhcnvA#D z!(4-cJ^lSwG88cZ6@iIg-p*Drp~b01#WBIjMVSRLE~&-YCHVz0=>;Y6#TkhysYNj* zsl_EmF+Q0|F~ylBsk#M;$=QkNsl_q*1*v(7nfhfw&6)XmdIgoYIBatBQ%ZAE?Le;i H48#lofOju@ literal 0 HcmV?d00001 diff --git a/lib/openai/__pycache__/wandb_logger.cpython-39.pyc b/lib/openai/__pycache__/wandb_logger.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..396a1604875b9423854f5ba1fa644014db662f27 GIT binary patch literal 7325 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 SZJ4Y{xr+L$=rx#W#rR+0fNbCZ literal 0 HcmV?d00001 diff --git a/lib/openai/_openai_scripts.py b/lib/openai/_openai_scripts.py new file mode 100644 index 0000000..d234256 --- /dev/null +++ b/lib/openai/_openai_scripts.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +import argparse +import logging +import sys + +import openai +from openai.cli import api_register, display_error, tools_register, wandb_register + +logger = logging.getLogger() +formatter = logging.Formatter("[%(asctime)s] %(message)s") +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) + + +def main(): + parser = argparse.ArgumentParser(description=None) + parser.add_argument( + "-v", + "--verbose", + action="count", + dest="verbosity", + default=0, + help="Set verbosity.", + ) + parser.add_argument("-b", "--api-base", help="What API base url to use.") + parser.add_argument("-k", "--api-key", help="What API key to use.") + parser.add_argument( + "-o", + "--organization", + help="Which organization to run as (will use your default organization if not specified)", + ) + + def help(args): + parser.print_help() + + parser.set_defaults(func=help) + + subparsers = parser.add_subparsers() + sub_api = subparsers.add_parser("api", help="Direct API calls") + sub_tools = subparsers.add_parser("tools", help="Client side tools for convenience") + sub_wandb = subparsers.add_parser("wandb", help="Logging with Weights & Biases") + + api_register(sub_api) + tools_register(sub_tools) + wandb_register(sub_wandb) + + args = parser.parse_args() + if args.verbosity == 1: + logger.setLevel(logging.INFO) + elif args.verbosity >= 2: + logger.setLevel(logging.DEBUG) + + openai.debug = True + if args.api_key is not None: + openai.api_key = args.api_key + if args.api_base is not None: + openai.api_base = args.api_base + if args.organization is not None: + openai.organization = args.organization + + try: + args.func(args) + except openai.error.OpenAIError as e: + display_error(e) + return 1 + except KeyboardInterrupt: + sys.stderr.write("\n") + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/lib/openai/api_requestor.py b/lib/openai/api_requestor.py new file mode 100644 index 0000000..8a0e6ca --- /dev/null +++ b/lib/openai/api_requestor.py @@ -0,0 +1,695 @@ +import asyncio +import json +import platform +import sys +import threading +import warnings +from contextlib import asynccontextmanager +from json import JSONDecodeError +from typing import ( + AsyncGenerator, + AsyncIterator, + Dict, + Iterator, + Optional, + Tuple, + Union, + overload, +) +from urllib.parse import urlencode, urlsplit, urlunsplit + +import aiohttp +import requests + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + +import openai +from openai import error, util, version +from openai.openai_response import OpenAIResponse +from openai.util import ApiType + +TIMEOUT_SECS = 600 +MAX_CONNECTION_RETRIES = 2 + +# Has one attribute per thread, 'session'. +_thread_context = threading.local() + + +def _build_api_url(url, query): + scheme, netloc, path, base_query, fragment = urlsplit(url) + + if base_query: + query = "%s&%s" % (base_query, query) + + return urlunsplit((scheme, netloc, path, query, fragment)) + + +def _requests_proxies_arg(proxy) -> Optional[Dict[str, str]]: + """Returns a value suitable for the 'proxies' argument to 'requests.request.""" + if proxy is None: + return None + elif isinstance(proxy, str): + return {"http": proxy, "https": proxy} + elif isinstance(proxy, dict): + return proxy.copy() + else: + raise ValueError( + "'openai.proxy' must be specified as either a string URL or a dict with string URL under the https and/or http keys." + ) + + +def _aiohttp_proxies_arg(proxy) -> Optional[str]: + """Returns a value suitable for the 'proxies' argument to 'aiohttp.ClientSession.request.""" + if proxy is None: + return None + elif isinstance(proxy, str): + return proxy + elif isinstance(proxy, dict): + return proxy["https"] if "https" in proxy else proxy["http"] + else: + raise ValueError( + "'openai.proxy' must be specified as either a string URL or a dict with string URL under the https and/or http keys." + ) + + +def _make_session() -> requests.Session: + if not openai.verify_ssl_certs: + warnings.warn("verify_ssl_certs is ignored; openai always verifies.") + s = requests.Session() + proxies = _requests_proxies_arg(openai.proxy) + if proxies: + s.proxies = proxies + s.mount( + "https://", + requests.adapters.HTTPAdapter(max_retries=MAX_CONNECTION_RETRIES), + ) + return s + + +def parse_stream_helper(line: bytes) -> Optional[str]: + if line: + if line.strip() == b"data: [DONE]": + # return here will cause GeneratorExit exception in urllib3 + # and it will close http connection with TCP Reset + return None + if line.startswith(b"data: "): + line = line[len(b"data: "):] + return line.decode("utf-8") + else: + return None + return None + + +def parse_stream(rbody: Iterator[bytes]) -> Iterator[str]: + for line in rbody: + _line = parse_stream_helper(line) + if _line is not None: + yield _line + + +async def parse_stream_async(rbody: aiohttp.StreamReader): + async for line in rbody: + _line = parse_stream_helper(line) + if _line is not None: + yield _line + + +class APIRequestor: + def __init__( + self, + key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + ): + self.api_base = api_base or openai.api_base + self.api_key = key or util.default_api_key() + self.api_type = ( + ApiType.from_str(api_type) + if api_type + else ApiType.from_str(openai.api_type) + ) + self.api_version = api_version or openai.api_version + self.organization = organization or openai.organization + + @classmethod + def format_app_info(cls, info): + str = info["name"] + if info["version"]: + str += "/%s" % (info["version"],) + if info["url"]: + str += " (%s)" % (info["url"],) + return str + + @overload + def request( + self, + method, + url, + params, + headers, + files, + stream: Literal[True], + request_id: Optional[str] = ..., + request_timeout: Optional[Union[float, Tuple[float, float]]] = ..., + ) -> Tuple[Iterator[OpenAIResponse], bool, str]: + pass + + @overload + def request( + self, + method, + url, + params=..., + headers=..., + files=..., + *, + stream: Literal[True], + request_id: Optional[str] = ..., + request_timeout: Optional[Union[float, Tuple[float, float]]] = ..., + ) -> Tuple[Iterator[OpenAIResponse], bool, str]: + pass + + @overload + def request( + self, + method, + url, + params=..., + headers=..., + files=..., + stream: Literal[False] = ..., + request_id: Optional[str] = ..., + request_timeout: Optional[Union[float, Tuple[float, float]]] = ..., + ) -> Tuple[OpenAIResponse, bool, str]: + pass + + @overload + def request( + self, + method, + url, + params=..., + headers=..., + files=..., + stream: bool = ..., + request_id: Optional[str] = ..., + request_timeout: Optional[Union[float, Tuple[float, float]]] = ..., + ) -> Tuple[Union[OpenAIResponse, Iterator[OpenAIResponse]], bool, str]: + pass + + def request( + self, + method, + url, + params=None, + headers=None, + files=None, + stream: bool = False, + request_id: Optional[str] = None, + request_timeout: Optional[Union[float, Tuple[float, float]]] = None, + ) -> Tuple[Union[OpenAIResponse, Iterator[OpenAIResponse]], bool, str]: + result = self.request_raw( + method.lower(), + url, + params=params, + supplied_headers=headers, + files=files, + stream=stream, + request_id=request_id, + request_timeout=request_timeout, + ) + resp, got_stream = self._interpret_response(result, stream) + return resp, got_stream, self.api_key + + @overload + async def arequest( + self, + method, + url, + params, + headers, + files, + stream: Literal[True], + request_id: Optional[str] = ..., + request_timeout: Optional[Union[float, Tuple[float, float]]] = ..., + ) -> Tuple[AsyncGenerator[OpenAIResponse, None], bool, str]: + pass + + @overload + async def arequest( + self, + method, + url, + params=..., + headers=..., + files=..., + *, + stream: Literal[True], + request_id: Optional[str] = ..., + request_timeout: Optional[Union[float, Tuple[float, float]]] = ..., + ) -> Tuple[AsyncGenerator[OpenAIResponse, None], bool, str]: + pass + + @overload + async def arequest( + self, + method, + url, + params=..., + headers=..., + files=..., + stream: Literal[False] = ..., + request_id: Optional[str] = ..., + request_timeout: Optional[Union[float, Tuple[float, float]]] = ..., + ) -> Tuple[OpenAIResponse, bool, str]: + pass + + @overload + async def arequest( + self, + method, + url, + params=..., + headers=..., + files=..., + stream: bool = ..., + request_id: Optional[str] = ..., + request_timeout: Optional[Union[float, Tuple[float, float]]] = ..., + ) -> Tuple[Union[OpenAIResponse, AsyncGenerator[OpenAIResponse, None]], bool, str]: + pass + + async def arequest( + self, + method, + url, + params=None, + headers=None, + files=None, + stream: bool = False, + request_id: Optional[str] = None, + request_timeout: Optional[Union[float, Tuple[float, float]]] = None, + ) -> Tuple[Union[OpenAIResponse, AsyncGenerator[OpenAIResponse, None]], bool, str]: + ctx = aiohttp_session() + session = await ctx.__aenter__() + try: + result = await self.arequest_raw( + method.lower(), + url, + session, + params=params, + supplied_headers=headers, + files=files, + request_id=request_id, + request_timeout=request_timeout, + ) + resp, got_stream = await self._interpret_async_response(result, stream) + except Exception: + await ctx.__aexit__(None, None, None) + raise + if got_stream: + + async def wrap_resp(): + assert isinstance(resp, AsyncGenerator) + try: + async for r in resp: + yield r + finally: + await ctx.__aexit__(None, None, None) + + return wrap_resp(), got_stream, self.api_key + else: + await ctx.__aexit__(None, None, None) + return resp, got_stream, self.api_key + + def handle_error_response(self, rbody, rcode, resp, rheaders, stream_error=False): + try: + error_data = resp["error"] + except (KeyError, TypeError): + raise error.APIError( + "Invalid response object from API: %r (HTTP response code " + "was %d)" % (rbody, rcode), + rbody, + rcode, + resp, + ) + + if "internal_message" in error_data: + error_data["message"] += "\n\n" + error_data["internal_message"] + + util.log_info( + "OpenAI API error received", + error_code=error_data.get("code"), + error_type=error_data.get("type"), + error_message=error_data.get("message"), + error_param=error_data.get("param"), + stream_error=stream_error, + ) + + # Rate limits were previously coded as 400's with code 'rate_limit' + if rcode == 429: + return error.RateLimitError( + error_data.get("message"), rbody, rcode, resp, rheaders + ) + elif rcode in [400, 404, 415]: + return error.InvalidRequestError( + error_data.get("message"), + error_data.get("param"), + error_data.get("code"), + rbody, + rcode, + resp, + rheaders, + ) + elif rcode == 401: + return error.AuthenticationError( + error_data.get("message"), rbody, rcode, resp, rheaders + ) + elif rcode == 403: + return error.PermissionError( + error_data.get("message"), rbody, rcode, resp, rheaders + ) + elif rcode == 409: + return error.TryAgain( + error_data.get("message"), rbody, rcode, resp, rheaders + ) + elif stream_error: + # TODO: we will soon attach status codes to stream errors + parts = [error_data.get("message"), "(Error occurred while streaming.)"] + message = " ".join([p for p in parts if p is not None]) + return error.APIError(message, rbody, rcode, resp, rheaders) + else: + return error.APIError( + f"{error_data.get('message')} {rbody} {rcode} {resp} {rheaders}", + rbody, + rcode, + resp, + rheaders, + ) + + def request_headers( + self, method: str, extra, request_id: Optional[str] + ) -> Dict[str, str]: + user_agent = "OpenAI/v1 PythonBindings/%s" % (version.VERSION,) + if openai.app_info: + user_agent += " " + self.format_app_info(openai.app_info) + + uname_without_node = " ".join( + v for k, v in platform.uname()._asdict().items() if k != "node" + ) + ua = { + "bindings_version": version.VERSION, + "httplib": "requests", + "lang": "python", + "lang_version": platform.python_version(), + "platform": platform.platform(), + "publisher": "openai", + "uname": uname_without_node, + } + if openai.app_info: + ua["application"] = openai.app_info + + headers = { + "X-OpenAI-Client-User-Agent": json.dumps(ua), + "User-Agent": user_agent, + } + + headers.update(util.api_key_to_header(self.api_type, self.api_key)) + + if self.organization: + headers["OpenAI-Organization"] = self.organization + + if self.api_version is not None and self.api_type == ApiType.OPEN_AI: + headers["OpenAI-Version"] = self.api_version + if request_id is not None: + headers["X-Request-Id"] = request_id + if openai.debug: + headers["OpenAI-Debug"] = "true" + headers.update(extra) + + return headers + + def _validate_headers( + self, supplied_headers: Optional[Dict[str, str]] + ) -> Dict[str, str]: + headers: Dict[str, str] = {} + if supplied_headers is None: + return headers + + if not isinstance(supplied_headers, dict): + raise TypeError("Headers must be a dictionary") + + for k, v in supplied_headers.items(): + if not isinstance(k, str): + raise TypeError("Header keys must be strings") + if not isinstance(v, str): + raise TypeError("Header values must be strings") + headers[k] = v + + # NOTE: It is possible to do more validation of the headers, but a request could always + # be made to the API manually with invalid headers, so we need to handle them server side. + + return headers + + def _prepare_request_raw( + self, + url, + supplied_headers, + method, + params, + files, + request_id: Optional[str], + ) -> Tuple[str, Dict[str, str], Optional[bytes]]: + abs_url = "%s%s" % (self.api_base, url) + headers = self._validate_headers(supplied_headers) + + data = None + if method == "get" or method == "delete": + if params: + encoded_params = urlencode( + [(k, v) for k, v in params.items() if v is not None] + ) + abs_url = _build_api_url(abs_url, encoded_params) + elif method in {"post", "put"}: + if params and files: + data = params + if params and not files: + data = json.dumps(params).encode() + headers["Content-Type"] = "application/json" + else: + raise error.APIConnectionError( + "Unrecognized HTTP method %r. This may indicate a bug in the " + "OpenAI bindings. Please contact support@openai.com for " + "assistance." % (method,) + ) + + headers = self.request_headers(method, headers, request_id) + + util.log_debug("Request to OpenAI API", method=method, path=abs_url) + util.log_debug("Post details", data=data, api_version=self.api_version) + + return abs_url, headers, data + + def request_raw( + self, + method, + url, + *, + params=None, + supplied_headers: Optional[Dict[str, str]] = None, + files=None, + stream: bool = False, + request_id: Optional[str] = None, + request_timeout: Optional[Union[float, Tuple[float, float]]] = None, + ) -> requests.Response: + abs_url, headers, data = self._prepare_request_raw( + url, supplied_headers, method, params, files, request_id + ) + + if not hasattr(_thread_context, "session"): + _thread_context.session = _make_session() + try: + result = _thread_context.session.request( + method, + abs_url, + headers=headers, + data=data, + files=files, + stream=stream, + timeout=request_timeout if request_timeout else TIMEOUT_SECS, + ) + except requests.exceptions.Timeout as e: + raise error.Timeout("Request timed out: {}".format(e)) from e + except requests.exceptions.RequestException as e: + raise error.APIConnectionError( + "Error communicating with OpenAI: {}".format(e) + ) from e + util.log_debug( + "OpenAI API response", + path=abs_url, + response_code=result.status_code, + processing_ms=result.headers.get("OpenAI-Processing-Ms"), + request_id=result.headers.get("X-Request-Id"), + ) + # Don't read the whole stream for debug logging unless necessary. + if openai.log == "debug": + util.log_debug( + "API response body", body=result.content, headers=result.headers + ) + return result + + async def arequest_raw( + self, + method, + url, + session, + *, + params=None, + supplied_headers: Optional[Dict[str, str]] = None, + files=None, + request_id: Optional[str] = None, + request_timeout: Optional[Union[float, Tuple[float, float]]] = None, + ) -> aiohttp.ClientResponse: + abs_url, headers, data = self._prepare_request_raw( + url, supplied_headers, method, params, files, request_id + ) + + if isinstance(request_timeout, tuple): + timeout = aiohttp.ClientTimeout( + connect=request_timeout[0], + total=request_timeout[1], + ) + else: + timeout = aiohttp.ClientTimeout( + total=request_timeout if request_timeout else TIMEOUT_SECS + ) + + if files: + # TODO: Use `aiohttp.MultipartWriter` to create the multipart form data here. + # For now we use the private `requests` method that is known to have worked so far. + data, content_type = requests.models.RequestEncodingMixin._encode_files( # type: ignore + files, data + ) + headers["Content-Type"] = content_type + request_kwargs = { + "method": method, + "url": abs_url, + "headers": headers, + "data": data, + "proxy": _aiohttp_proxies_arg(openai.proxy), + "timeout": timeout, + } + try: + result = await session.request(**request_kwargs) + util.log_info( + "OpenAI API response", + path=abs_url, + response_code=result.status, + processing_ms=result.headers.get("OpenAI-Processing-Ms"), + request_id=result.headers.get("X-Request-Id"), + ) + # Don't read the whole stream for debug logging unless necessary. + if openai.log == "debug": + util.log_debug( + "API response body", body=result.content, headers=result.headers + ) + return result + except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as e: + raise error.Timeout("Request timed out") from e + except aiohttp.ClientError as e: + raise error.APIConnectionError("Error communicating with OpenAI") from e + + def _interpret_response( + self, result: requests.Response, stream: bool + ) -> Tuple[Union[OpenAIResponse, Iterator[OpenAIResponse]], bool]: + """Returns the response(s) and a bool indicating whether it is a stream.""" + if stream and "text/event-stream" in result.headers.get("Content-Type", ""): + return ( + self._interpret_response_line( + line, result.status_code, result.headers, stream=True + ) + for line in parse_stream(result.iter_lines()) + ), True + else: + return ( + self._interpret_response_line( + result.content.decode("utf-8"), + result.status_code, + result.headers, + stream=False, + ), + False, + ) + + async def _interpret_async_response( + self, result: aiohttp.ClientResponse, stream: bool + ) -> Tuple[Union[OpenAIResponse, AsyncGenerator[OpenAIResponse, None]], bool]: + """Returns the response(s) and a bool indicating whether it is a stream.""" + if stream and "text/event-stream" in result.headers.get("Content-Type", ""): + return ( + self._interpret_response_line( + line, result.status, result.headers, stream=True + ) + async for line in parse_stream_async(result.content) + ), True + else: + try: + await result.read() + except aiohttp.ClientError as e: + util.log_warn(e, body=result.content) + return ( + self._interpret_response_line( + (await result.read()).decode("utf-8"), + result.status, + result.headers, + stream=False, + ), + False, + ) + + def _interpret_response_line( + self, rbody: str, rcode: int, rheaders, stream: bool + ) -> OpenAIResponse: + # HTTP 204 response code does not have any content in the body. + if rcode == 204: + return OpenAIResponse(None, rheaders) + + if rcode == 503: + raise error.ServiceUnavailableError( + "The server is overloaded or not ready yet.", + rbody, + rcode, + headers=rheaders, + ) + try: + if 'text/plain' in rheaders.get('Content-Type'): + data = rbody + else: + data = json.loads(rbody) + except (JSONDecodeError, UnicodeDecodeError) as e: + raise error.APIError( + f"HTTP code {rcode} from API ({rbody})", rbody, rcode, headers=rheaders + ) from e + resp = OpenAIResponse(data, rheaders) + # In the future, we might add a "status" parameter to errors + # to better handle the "error while streaming" case. + stream_error = stream and "error" in resp.data + if stream_error or not 200 <= rcode < 300: + raise self.handle_error_response( + rbody, rcode, resp.data, rheaders, stream_error=stream_error + ) + return resp + + +@asynccontextmanager +async def aiohttp_session() -> AsyncIterator[aiohttp.ClientSession]: + user_set_session = openai.aiosession.get() + if user_set_session: + yield user_set_session + else: + async with aiohttp.ClientSession() as session: + yield session diff --git a/lib/openai/api_resources/__init__.py b/lib/openai/api_resources/__init__.py new file mode 100644 index 0000000..b06ebb4 --- /dev/null +++ b/lib/openai/api_resources/__init__.py @@ -0,0 +1,14 @@ +from openai.api_resources.audio import Audio # noqa: F401 +from openai.api_resources.chat_completion import ChatCompletion # noqa: F401 +from openai.api_resources.completion import Completion # noqa: F401 +from openai.api_resources.customer import Customer # noqa: F401 +from openai.api_resources.deployment import Deployment # noqa: F401 +from openai.api_resources.edit import Edit # noqa: F401 +from openai.api_resources.embedding import Embedding # noqa: F401 +from openai.api_resources.engine import Engine # noqa: F401 +from openai.api_resources.error_object import ErrorObject # noqa: F401 +from openai.api_resources.file import File # noqa: F401 +from openai.api_resources.fine_tune import FineTune # noqa: F401 +from openai.api_resources.image import Image # noqa: F401 +from openai.api_resources.model import Model # noqa: F401 +from openai.api_resources.moderation import Moderation # noqa: F401 diff --git a/lib/openai/api_resources/__pycache__/__init__.cpython-39.pyc b/lib/openai/api_resources/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80b40ca892581eaac38eace11825e0649ae77b0f GIT binary patch literal 1046 zcmZ{i-EPw`6vvacX_K~TJH{Ao`4AE}y;vvh5+MZYI%yIg7lRusQj{iMXJ*IIv3s#+ z;7O3+g?7soufP@OcrcihCW`$#_CNOV`8$i_u7}U*_i^#1>3KgW`2T5O@ESk!4V%I% zeCa8l`95(&HdK=}9XDl51uSsfl5N#t9mjzTRm394ZP`^l)^psEeHF9VaVQ6B$cBz1 zIZ``p$8lHQQ@d=}aZlb?du-2fUp`R#Y~OJ#AF44MJ08f1nzAYK@XVWyeqzSn%)bua zYzm9x$BmN&rz%_1VDL^CTq1M{Afrs)&mOUfu6UV=c~*%O zxX~NPxtV8_I*9Zm5STiK$}$K+Lbug z3_3Lbq<;QyEg>Y)v|i+D3=t`w)hnp>sdmK&y3AYBIY(2Vw>_P6G%K~}7W7F}?}s24 zA>J-fZd`{Us=j(K!p=3n*y2U<$#5{qSq`E~j=8zCx=PlSP0dAyKPHwN zdz8EttHcP)52`G`#EeXGQ<}p&x92cTMJa5W&a12I@FniNk^Gg9+nEVAeSh2g8+FAx A`2YX_ literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/audio.cpython-39.pyc b/lib/openai/api_resources/__pycache__/audio.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1aa35c64c891a8dfea533f24316005e630dd151f GIT binary patch literal 3408 zcmc&$TaVjB6rLH|iIdn#x|eo)FLwfT7s};zb z^VEH9e*g$+SHQzQ@B{b_`~$!8#4i99iE_?3w@p(CDp4l#@tn*Y`<(NAXM8-{E)y76 ze>%#~A135iR3?WG%0;;1D?ZjuW(CgCD4v@n^%=q2JHl|@w(C~pq=E0_?*%#&`$ArzMwQ4w9|Z% zA68nG>k#sp%vkMlKgstMcF(n4BHO-}C_{qhFsS2#;GpQfcBY2QjK zyE@lWmQt>-YwK)}Qj(U_N@}fZkPV~R&@&SqUGrj*&yuZ3WEK4HD~KL4l3AhL@VfrC zhxy5jPRK=&>Fa(VwwJsp3jDTOSqa;TNY2KI6kexcNaVa@Dbfw8SjbZ&Yp?_fk04RJ zVN0wXxq$zf_|&e~5po{YBD!%~Y$*d-$8CpDAh=l}@}$>^v(lLA2Aap2&b`D_Fy;ai znAP1#3NR7F(m*HT6CjAKQ$|g=;P+>ts5AW`W9%oeca79e z6|{Rm`+dFXR1@iSW8@;j06|7!oa{4s9Ns67g4i#}lW@y)mA@)Zi-Xii1GS}g*ab`z zHwoPw2sd2Y6zyccmP3F!S2)NeoMBi~NmnnD$8bTt7t@z%K<3PpB96c>kx_R#_2r@K zYvNTfh_S$kb&8hh64j~saJ!x-dA#lEr`J+14-}JQc^Vv)XHYzY;sq4w^#q?vLj!RE z79NeMHW3p%Rvaj4r1FR{O;84m(FS}e4fq0V0hW+ms`z53wNMwS14h6D6K95WFX~2)*(!W7nzK9ktp?Dd^RCY#eU4we_ zzWx-p+u4D%B)Ak4UPCVPKD^DjU= z>EUn{8igz5aYsZBd42@QXE7Lc0+?{%snd51{!BJkym9eup{K~uyhgD?=QLHPhw@-2 z_Wm!SD%4)b7-tn`Id>1ixc}Hl1}DyV>rC1y%x=O%o~%4ai2cJNeFH-p73oY$)+Ta~ zRQrAup(?BitQH@^N>OvNu^660|81BGt+z0=Sv6K2LxBGM{MdK~hsD9|(81t%;#&>c zF&Dp=uA7xz7h0QMfOE@r@ASMNUvVm*y!YYdOE=x->c^L}is~FYBDodv{A|Uqu56&d zubW&(v4Y|piZ@Zbjp7{?<8xBYur-m`++jZv@8E>$jM1{j3WN@Rzgx$dG1C69=BBjC zo%09m<`3*#n9?Tg7ANhLM~zZefM(F|ZbCAQXb5e0U3il@%WypLEu(CaRHYtRHj eJ1y}Or=B${?96;O&-Voi+hR;Wotd;wE$we9)bIoV literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/chat_completion.cpython-39.pyc b/lib/openai/api_resources/__pycache__/chat_completion.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16139ca42a6023bc0e87c99e8af7664fb9d8e199 GIT binary patch literal 1537 zcmb7EO>Z1E7`8nhJ4rTa5uq&=hq>+{yXE76Duf72fFKAcTaVekc1gCL2dDT%OUDa}~KDE6Jy$=t}T`)=xGe&kc~4UwMoUl8dl_uPqEk`73x z^$$cL2OV0uQ=6pFzO$E)lU(iow122fG1akxm%#&lx;qw09^>a%3(~=2J_S%DqLM^R z(#Vl4a+UXzjHq;^`yx1}k-r!t4IH;#5|=~)(x|rj6Jh&BR;J1(MgH~dW(+U7@$!v{ zX`QG6P==sRH=vq1fF-ZUYjWny*()lkWUtwoJKMJGoIG*nG$C{L2mOs6kpZN%?y05Z z%%f!X$=sD}O7&s0e(ucO+vM2QhtG-ip7_$4d(!-i!a|6wTa+pn3Ch!xMQnPaOm?&yDXsEY^)TzsWmeORYf{UQ$U=l^ zVr*E9!lxoluIGNUm#SrxOchgmF#F(0BsR&%^$xN^sx-7k_)O?5oR;7aPib9fI7kXROdxxA0S{E@-vJ zd7KJkIKLqOivIoK&jvIa4q}~@!>^P%wnaG{mzJA}kV+3hfBVt!`{eP^Bv$Q|B0ffq zhmC3|7S{`NgEk-*zZ9!mp6WJa#%gW?kRYIKhtX}iMek6?X7BZ3MSp3`ZnM3&+Vgh| z@*Lo;_Po1l&wa4iw_vq}NoOl2eYj%MhyTZ<8m~;JrPrX*cy;s!K-Chfr=vGSY} Y&{|4a1)nuKe2zagg9HQCrtmob0siWVp8x;= literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/completion.cpython-39.pyc b/lib/openai/api_resources/__pycache__/completion.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..357a52e3b637f5b3fe3dcd8bd928a8a6b3c4eb50 GIT binary patch literal 1583 zcmb7E&2Aev5GMEElI$dn(I!R<>{W+IX^~S<6m=D&b&$G&mBQ&l06{M)d1LNRNGgE^ zxraDFPHkTy0ebPF57JlQwWq#9PaWn#Lr_mA7`VSvzI zcCy+$n0yUgJqE)N!vZCCbOg?w#KG2gg`0bcm-~rd&pi?3VG`E9FQR-cS;OdC!~zz+ zMl9svnVYm29-?maH$+8yU2MFm&P3NSn-93)njQ+i{djkut8yw+Zf-ux)YXr$>Bdem z$%@sL@G*KKPqrsCD^eSKwb`@SrtX2^D8URR4#SDdy*Fr#nZw-I(HTzs#oS|lJrCX> z4<#Ys4NPlL<`p2!$|7l}OJAk94&4|a5?NZWL05Ay8ofpD(5X9j-eLxc-Z`hEQ1{Yp>`0rFm1@q%VOh}&mns`|9b?npl7k{2oO zU!+t0v<6bArLlnOE(4e2_yrZ&<+Shdn^0#tFQ9jGL6sup9Q`vI+#mg@fS%DXm02}*Dav2#^7Rc zbE*$2@i!ab6_^hyaqFrQcR@|xfvOgIoL$l5(<^#>`hWB&BY@EjWg7-*4<^^an2=sA zinP~9+EdGqt+{2*ZELo`#LzEMvh|*lk5(vI1nov9BBTHn6JlC~fLx}+`W-@^PpN4B zB<&x*dAKupLiV=5-!bjFz@Yo1lGR11)0*`~M literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/customer.cpython-39.pyc b/lib/openai/api_resources/__pycache__/customer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f466c619acfb7f42a1db59e0fb1184b7891f4d2d GIT binary patch literal 929 zcmbVLJ8#rL5Z;IHY%UiFhz3DH>w-H(i4Y1pBou)z#fiqUwAy%A7Zbm-yDm7H>hdol zq@=upKeR0se<2mK_Ps*MNHaULGoJZoX3Y-|2MFlo*E8{)AoSZA`{aRf46I**5J=z* z(?M8im*Tt zkyKzouFw)k9_Unft=3Ei?w2D7UePPNf zo>zwHb1tNcjnw8%{3Sh$b!z0RiYFI*E_GbOAb2|FRmzmqWvvpaw-`p%ck9okVYSL! zH4Q63h%Ama4UX2%dyTqCANVt{egUFEOZ3BQyc^dtx`DPEa!VR|OJ`_Brq-)UZ9-yw zB`<5hsoc(2J=KRpxlAemVfz(VJcn7Ng*LoMZ0$Kn zGOl$l&3P%>;i$*1=#%@E+xOs_SDUA%hhPPm-~k2p2yQ*okVa3ty^p$smF?0Q3a?%fEt>p1hr{N+ZVHvaXr(-iFfVJcvEGS zzK}|N0`UX-kV?Gdfq%h&K=Lb3{DnTWoHKTI*I_H6wy@G1`}mxh`Q}__{qgY{f#1q+ z^X!jFLjFW%_-6rg5r+H*2qTO}q)lB)@m-6ww(ja}!!_EbYpQR0WVI`9rER-5C6@^^ znE8M(lbhV&`kv`lnRSCSDu2Ohq(L*In}$(C&*qkR#8dxv#22rxtZ^B4MZmM!E1~?y z$77cS_fvjEx1nXTE1hjW3fUUJ+vPG{79tjz4eP150Cybz83iMMhZjy<%808m>gwEJ z8q*(;J?aALhvW`br?7#y3f^Ls2gp}I*Jf3vYZtN_8&k4s!PnWi;%lHg!6udNSRtEY z(@It^_!%~<_;JvkW5<;4L?JuQPAJ)A!JlOFil2H&4B}3MRZnHLC7wj_PMdd9cZ~Jl zWk82cjxNP<4TjtYvP<^KuC`AZnBcL7qov)~?;E>%s%`3Z_UUC!v?_B>c(6!5N)6;lfYDxFa23a3}6WJC48YhY?1Hb0-!~y1|{r zUKfNA2o5gL%*=Moc+|UawJTF65%G4&xZ^tu>hpq=_`+}Vl*8oM2~@Cwbg*j^;?`73 z*@O)IhNGm7tV(XcW-h-7B?VqIp|);;x{z*DgPE*bU2ILtX9 z;uJidv@U(#x+P)P){Q`fNo$GAtu#(r>q+X#4WDt*N+FTYx2}Y@TQW@fS+K&Ezs_X~ z9N+Q7bBJFQ)k2;d1bj2u$;Q24g9lq)?m^sFeg=eS2CYA{^*O3h?U`Xr;gf1lEwfJT z{<}>lo@tu(R5NmE@8ppGOAD7$ONaHbu0SOv@+}~{WRvXD5Yp>c`ZH9`l^z9MG)lc|qh$>3Pa2;x&|=0df|GR7KL9qP^*%^XH4b?7_up*r32E-hlV4 z;^!+12RQL25{#1^(%bO%DU1Q6nITBU%n~LOwEK1L0x&O9=NQzV1ASFgVZOjtc~BAP zT``3OvqVfInL#p(1XJU{JYr~Z5=HY!)RFq<%EpRlilEhf3353Bq#UHZ<3lGMQZJqM zg+gx??7x7Zl>H6FFw*!9(76mFH@*h(FgXMXr@br|@jfhHLSsTNe-4}vK=(=s&eRYb zHLrx{l|_Ph2X+r5qoD>HqTVWRZ3PT4Tt!IY*Que0ypr z4v$QERo-pF*131>Lo_3ec@FgAk}+q8z#UF~^{A?F6U&V>64u86DJ{W*yZ_;(wb`HB zn8|F<>%c|K^Rk-f!QHG2H!Ux#d){5R6XgIotu9_&&c;+DgR9#{%vAeR zeN15I$g3QdF@eQHs1(5r5Eyep*%S>X5FT6|@}3y*C_3zmD%Dj_QXSF;J!}W^JP?~2 znnr79G&)hqN39A^xI5q&^)7J!YNO^_`AtT368Ch@G)vc-rhgmCpdX|HOM<{`6&Utz mz0l_PM@L=8@@p9`Jye+nt;YT$x8hg$Z$M&etkXGjO8*=8pE_j# literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/edit.cpython-39.pyc b/lib/openai/api_resources/__pycache__/edit.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25b7c579bd01b55db19122914b3da0959de4e3eb GIT binary patch literal 1700 zcmb_cO>Z1E7`8p1Nj4#cNF`B0BXL@(YzyiMRfr{tAP7>_q(mL9WI1DJGs*1C)b^y> z)rLb;sh1X{{(?l}5+OJtPW%#IIq?^`z-#X&N-H=ZVabm_cJ_TAKYkyx`T1D{ZR4k9 z{$qsD?{;yu`LOr|x>^I^h~omKb|eKJkdzF()Wde|75*Sd12^}Ja1f;tMxP-b@bD?( zp$JaAH0F2t#PLMfdBC`$AQX%mi%4+j7|Lv%Jjg zzpggR%rAB(07#)LjqdQ}tlLpJ@lgwjp z>|aNRzI^@!LChA9#u1NS5R7)vL%5FP=-cSg=48B$ZX-M2LgzE1hqKAFtxCbPPzg&) z@i?((PWCF9=zWpYvO3DS;7MNAjlKzk(z3~PUX?c!R`R4~k`06wQgtrlnMmgYQ8jwg z#H`M#9@cPUat73rR?E@d?fqOORV^g5krG&xl}=Ps*OkX-HimU zKN$+$8Qs`nxz5YJBRQzJC=y*IkC_}KO$~WGNYS1*z9nJ2x~k1G?F&sWs8HZdkAN|x zmIOXFQMb;wfusq#>4W?0CUyhuuDMudVWT`^Mb3f2CDzS~o|7Vp}-6a_Sg$%ie3J z9?Ca?!yI&V0{{gvZjlyV#7o%6vvZTa?#GN0howg zsQ~$w-Cwe>V!^5_-?8v6zNef%ezk7qT={?>_bcu$+9fyh)4Iw;QBXRe=J+|Lj${Y~ZFMRuLyUbw z@WOS1rQL7OIC0W&Qdbjctjp35COe&1Dy_qI;gMjO?g;m8yCT1+Lup^+CB8p?=mm~Hu%#GMcOy9(M&5W(Y_U%HqVkfEiZc_ED#oUf- zN!_nw^f96itvo`s!t5i{Z&19AI__U!9c^~7vPLqBVVqw}hf&H_w${I3A{+6Lbqr;v zqa;62Z6XBYGT0>|C|2OSdygNi3`vxR+S2)^Z=jo42EmXozV>>GSTx3D2`f;P&JHa}%PmJT{3DPTf(EW|EvxBzbBC>VxJ4O>1qr>H~ zMU4^WPfLVjb8KBj`xbxt1Cn!lZR(C)T76<*w1d8e1W(-W-TT+d@dmnqw7!i_Ylpwp zylGqIj7TOt;-&1NHw%zA$haqWnV0kIK|~q#qBI{#kEGPg2`34YjEinGTHLC6UVM6TxmR`_-9!zcPq3BHz2&fl{IJAZLfK?T|>qM}_Ak zqb&Ah<~<}l@kTk+q|G?bcxPT&+L1EzELY}`No9*s&bV^IB5ul}L=rdzHkBzP=Qfm& zGSXbt20TmpqXFEO>*7+?{;g%o04r0D%=BHk1MA$-n{J#9gD4$jr&VQ!u}~J_L!q2~ zfCQs0B;k_lLiJxC0fU0Gyabo8?{X&cEQK@k+AwCPSXF}{jDdMUaEku^X7zUOOV}2@ z?T|-#?=BPjGRu3zTn1v7P{w;u5b|d4)2QDQk!06%67G{B6TLE@I*kE%DX1-;r?HzK z@HY5tK^Jd>K(31$M#HqQYZ~~9W#O9H#tyb{>z}se;1*uMhH=ZUK+)xYQFKeXdsSIGu3sW&V@A%c89AFTM9$XU+>4QuyKv7=n>S$K@Tw!O z!^!p2q$;G0Pmx)EMX$f4#mic}qJ=Ir{;C#NwV2fxU(nM1SvcUL7-|u0DKz7T5klw{Sk@F9l!qhM;mLa_kzup&(>7E zC`folb~9R3O@Tx;r35pvan$GfmsAE^Gu>}Yw~ObZjJL*d c4)`q8%Dqzt<^%oj5gMWk#Ax6KZsUge3`6n#zW@LL literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/engine.cpython-39.pyc b/lib/openai/api_resources/__pycache__/engine.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2ea651c8307e577f538ca81e81f272b175f4344 GIT binary patch literal 1607 zcmb7E-EJH;6t+E{pWSTIgbLCkDy@3sFUu=&c zt4+mE#9iMZdzlNKgg5XlSG)pT!12s(nua2TB_I2Y_Z*-7zVn$aFLx1)?O$&4A3TJ9 zv(Dw=f^!$98iC=6;|zuEibD)O$w=;m4z}N%jOK3W=3eMEKF$0*2m_2hLfqxvDdL`R zh4&K;I~?zzf&V+?M56&VwAN`hAm;jmRB5)KiOnyzzZ9yhWg^VW4=c{JxbhjIf?avM zIc8~**gThq1I}HT>NXgGY^5RLICOZ2JDi^4kn(O*6AoSO^WYSP9w$)KlJT~RaaxGb z7tN|h34tVwVeznteF{^34@RRiG(#uORQ(R^|1}NF&wLO;Vv>5jf zO4-j#F0#HZ``3oR`m(jsJn ztQ^N_aZsMShANRA7~e*hwKiVGB+Hd?AzEsK#W|9%K+GRs-Fq*3sDxC}P9oDPx-ZnB zE~{u<=~#WkxR8;Ci{FkOr2COdwRo#y$srpH6_u4JSh~TgG?w#LQ5!91!|K=s3ov?c zk9rjh!T}+eVuHIc15Ex5{1toy6EeBhaz0$dypZx=ZK9vSTu}bze^I^-w0#Wp&53_< zh4|x(G@zL@kEx0efYmjMrfUe-8AZ$qYIeRN-&}mV!1M&<374pf)<1ygN@S@_oTM z^rY&is?S9wMFOqNZ})2@`df?BVFHbb%7 literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/error_object.cpython-39.pyc b/lib/openai/api_resources/__pycache__/error_object.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c86086bdcdc266a60f80432380852d83e7c05b0a GIT binary patch literal 889 zcmY*X&1w`u5bo~z*-ds9qvl}Vzy(dflSmLnMFO%p1TVv2)0wW_Ofr9V_XZNiQvx~p z26=%Te4)O2@)44gRkI7O)yy|lR8M{NRgD^tHyPT&uN~b5jQyry-9ilB;bUJTXr_6| zDxPvqz9>Z%q(ScoWmrXN#Mx)2Lmgc+9YJsvq_K{V*kterzh)Eehli~znp&0UCx z7j{t>Yw?qag6Tx~K?T;TIrz|Bw1DLCxhfZ6{Xn&aJcA2ABIg{8#Tob^1$5IVjhU;u zxKuP76C?Q~G&Ff1*+mP+kb0jOm|^!Pa%P%pGVq~=a%MI#WoU5+_Zw2? zrLtDaJNEGH!<+1@MgHt4H$|I$1bgP1Hk-Fj+7qS0WDcx*m3=9WGg~-#*{b{uCE2XO zWvOD1dd4Q2g~`G0VQY<%O~1k2_QE_uP8y zttybxZ%SD;dQp;}NcnxC%4Nm~EbfD%c0HjVuO#MBmQtFhm|enS^0oamTogqMrBl)9t0=-Q{Z~($Mdbj#jCKE6RRPeT6mRz6B!o!~La(x188% KCy7XS68r^W)!uOc literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/file.cpython-39.pyc b/lib/openai/api_resources/__pycache__/file.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a84846896ce0fb80114f7c932608df1435107ac GIT binary patch literal 4705 zcmb7I%X8bt8D9X42SJclY+05Z*=5r@9Zqd4O*5Uw&BKl#i8GGk`qc?W9Teh15-EzL z7oc5pz)r^Yu|BqkdeV_*deNah^xRu7{TI5|o^tA~Y21VR`xc}u$y6WU;KP33zQ4zB zzhzvh_zch0-_MD+Pcrs5Vs<|^nDcnk7KmWNXtS{R8leH+Y?~WaXc>i!ha57i%{T1O z-f%)^!wp?s$J^dUDJ*UHp|5$nUET=7z+fLT;RyFJ6K>)srAIuh2;(NJdtYKcwpce5 zZ^UUWw7r_GKecN3J!~ z{Xz`sUg$M%J?JG3nq&9lfjN&i{Uu1kXosOGjL=HBI4Uf`9~+O1&;~anY~g6m0e4Ke z!qc1!ZdR0puQ?CgoG6Pxb0u)cMMYFK=Yu;WrXDklSVs9(@rpRCYXWfd;)s~mTt%4B z#|g!+G~0=?S3uLnhPE0ONzWm?F5ZiHQw5T-mXY(^?o~6hW#vxp=AJN~*h4>Oo1FM9 zbIBZ*hi+cZ?3RPqT`})5c?ShJHV!1^%TZX~%lCJrau5t2GZV1d$)ODr{ zsc?2nb3d=-)fJ2UVsV=*k6d9nPLraP!IY{@vU`b4o868Ix^gw{GzW18ZZN%YDm|6H zj|OK(4cqUHtzM>&4wOv zq1(uk>`a=;B;Kf3r3IaBLPLP}N+tnq(0zkmiWuju@|0lE*3gLU{Ye zW##Elbm5XJHQI5SMt#{f;RFMB8RE9OXI935A?cZh2&KtshzAwA#y}bS)N% zT*?r}Z!TSHE-$6cEIHGQ8|(3Ek}ef`TcCByQ5dHSw3M^G2jS6(l+cTz5E4ZV7$8e% z2kDz2tilb`@bKdIFUREuf9~-qNDdpm;hR-n)n(7%M)2I>)8^p#6&iZturI|}88he@ zyMh9ufaOMR<`zX-5o392tF&YdeMDA}`z=Elc{xWcJ+UCI468$)nVS}TnNvh=@tbVh zT2vg)memcdz&cmQBU;kL6)AUQ3CXbnHQVTR;Lur=b)!OUQFpnOG_vhO5?Idm8DD;b zWaNXW`zDcZ6Cq0~58Cf_I|u_K(nc-F>s03ydVdGylqM?luC-q1p6FM8c?Np+ zo)5}e$3>rsO8qkpbPeSHQ|eV5Iv=rvR4c!O0qb1W@d-V+j1En$Ir&}4T z#>Fx}gZMvgZNAuAptm;a)F2QZKjbVsp!{nv8kFHKBT&{(qe0olt~oi824!z(=iVMr z{@Dm{O9d!D1%N)q!J8YPR&GCKU${fRb5{2vfd0H_zte_)!BJm;wVm?dO3s@ZcoyV8*PP zgZWAEKOSh0K`AO|gBk%^DC~FEEeB_+JM?mQlOo^SGKXZcQp@DbV(W%HC#-drijIjh z(8al2YH_N~>`fP8q8=TB6m zK)#xv1RUe7gym5HL^8gw%E*ecei}7!z^S8aIFNuidK?1XBJACfGbr9lairR$*;JZX zfwGq$0KS#GmJpPub-#ccg5dvRP%@)(Fh{?g?T zLiRyL+il!qkD0M% z+60=LfVwSX=;YQV_R|&QtgbNTSkBii`TE`IQ43d?bI3vweO~UM=KS4hfTN3?_72)x zXm3J#2U489koGdv3OBHR zr_)>AcdyDR1zKq$f!y1ffT2*oiI72(6W|D@#K(jgOc=t1WGC5~FhGOUKYj@6cFrzx z6kGK0my}P*Pe2ysA}BOv zHO^ngl9YqH8PKG_d!3nC5ElurjZ%1`xFnq2bGJGhFT*F|MkXC&b^4=*d@g9;AmeNhkBlIyn;Lem0SpNoJC{z0Y literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/fine_tune.cpython-39.pyc b/lib/openai/api_resources/__pycache__/fine_tune.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf66e55aa5739e928f4a03e98d4da0f0944b80f5 GIT binary patch literal 3652 zcmb_f-I5ze74DvY&5X2b?|N+mcFfP3$Y5=7AXEk0$>LpLO^{vjI=~=mm{qqnT8~EK zo{`H|jkvH~*c4@W18QBBi|hq&!Bg}tSG+;8q$-@#BPpwm5(BP={?6B} zs9JRiJX?P|%l@)V$Ujgy`q-en4JG?M2u2u9I?Ih5=vh=S~smtVd zzAI>BuX*!tJ2xMss+6Z8clXK&-W+q|M{E00@^KaFIcDudQZ7|7hpo z>AOibNYlQ69liiixRAs4Tv*NOuB?>%JP@(az(095+~s+Qu);B6l(29-t?;nlTo3NP zyD1&@3fDK}Vl*5G7zkgaf_J(PrG4x6W;0yh5*RsI+e#ipahJie2#UHd^>&;|t;?j1 zAKl}Rq%&Dhx??bh(CuOCcyVc^k%)SkG4ij z%cV7~rk$k2GZXwZj!iNH!3xZUkW>(CKm`D$paHNm0957#AOJuc0BP@00G(49Onaf< zAqRR>8fhOBP<#!(u502Vyrex2^g%_O0bQI$@f8$bMS(-bITYtnd=mw_r9xVYRZyi> zy8S(P+TZ3LF;i_?WkOS6Z*c` z*u|-_^J-t9mFbYU}MqikaxMbfzU6F)8vI;*p z_be#3B8guC4Uj#js1$BtDUzrHN#K|YkVFcU06mH%s!1((_dN!)jY+~Qs)`cKX5A>$ z2=k-dL)<>;_r-3Mbcc#}hnJ?bp{O}~E6d@C=v~XjfL{cdAEl5pC53!`pjd>7>%KSv zFGU(chg#sPGiH1g2}}gV7-9))hz)TH#R>{!25}lhy>=Xa2=U_>T*Ed6#8~|o_ynRl}ynRleYaJVM*Fkbuhlg8KRPMTk1Bfu0{=#hOBX8vYk>pz8<@ys%+`LI1 z(`V!veWrEbRAlB;r|>q&6AH(I)s#Ar_x6D%t z^pZmqbEsWI3KohgDpz-T!XKvM`d@Kz%p}9s4ySza$*?|+@62{JuEza#6ld2P(;4c9 zK*|wy5Yp{t6V4ZSqdTbncbIw&XF~W$2UuR+Vk8Yq_IMNfl=;;en;t`ZhQ1Mh>Ojfx z9~+MLk?R@M{nR&Vv}S7`4_D?pHWx2{!kIfD{ujJ#!QJ#{+wKsVAqo}5DGJ+mtf$#xWrWyxcTB53boj0dsmyC>_ZsxA2;vhWb;mcfk|);gJ(us*aB_G-=1{B zFo}9R45b@}y*?YnSog#5{ve9Wp1`~HC+(l?(Psqf)$ zOiI2YyughIgd4)VuXz?{w@AhO8={d4%e0HltxUfW#Yq*sQqYckDeiVeoU~-7cao@H zp_y~>=E}DsZgpfRLL4}Jnc!T2JAMd85RY==F-|>A=$vuw0l81T0+f%dX;APC@MgKm ztq16t;LY(Ox0PpsH_sjJDz6CM0x$6?<=NmZ@*}*gJO{iJe3~Cso(tYdevHp3uf%m& zoj{ zH1;oK6Q<-k-0^KN3E6-(rK~-bunjHMQavqj`l!$~VZ}Q7yR62#R##8WodRc2YjACs zrpB&T)4D}yp^3R;rA3GWV|M}CJ-xc8XBJL+HHbxCO19b}bFl6nAOYxPZcDBQ&1gG7 ztTMgbiWARS7fm5mOOOVCjku=jk} zYQoDCKWX`S6u-5)A;M(OmPPDfVxrGm?89EiD>$FR|82Qp~HS@A#t)Xyv_;&!VE z*~L`n2D6bFp%g(PaGLQ+FvQj=qZZuo`^T`#OsDR1Ti2=eeA~@)mJgYjuqPPD1M`0p zwgOwiRdQI`u#8ytlIOSK+lfpn>%C~GOoA_T;5n*4P^KqU7!ctm7KjQp)u;Ew4?j)J~l>4J2Qv$|EoX&%PJPC}(L_ zP;%U#58CZ|6spSQR+xz7T$}&}8he(a@gzhQHz7bGfL8_t5KUg0Qe2d8!F%KhG%5k3 zWR%D#c?!*GFqsB>Kvr0b>LSiGAaGW=7t}j1aB{(KO93<%ex4hDu#6RmGAT=Dnb|

X zT^)y?%=Rbf^^&EY%$GVhqT++mp#{VH7Yb;=i(kM|dWH{8PtTC$)Kq#d zDm`P?QO|bQ80gsnJ-ewjq~}t1Ds@56CD8NKke;FL1wAuPI#fQy{&4Cjq-;p*MPQn% zv~KjYUJyJ=;#`k!!*nyf5yYGF9Vk|g43X?3sCZSu$^*K+hr`}SGnRyzak1h&9z)0Y zEEwXB?4~0+wNR^zleLOLM$&=XE2if<-0?Lq+}RV zJtELU0eXVrZi0DzfClRPZQVC4Jw4Ql{%|Dl)l4eI%zzVU)LfT%3YYlTAwNCg30Cd4-l_b^xrAp- qI5euiOYu^$3a144yCCtDu3}AEZf1R+L;MAXk5R8>X2ECK)&2$d{|D^= literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/__pycache__/model.cpython-39.pyc b/lib/openai/api_resources/__pycache__/model.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..292f98a335443314f38aba7b2ec15bac9e5de3e2 GIT binary patch literal 463 zcmZ8e%}N6?5KgjNsntS3FCM%KJ=lUr5ov9URN5+55tcwmH$z>z+ubJV!Lzf*b5aoxYUCXq-5hHdB1Gao{or54irA2yIu~t$@ zDySForS`R#UfTlp(TDyaU3=LD17(0L5=Rddz z{b?qP!-2^Y5cM@EjyTTI3`clIA~K^9#b%A<&diNmjJ`vha_0hZN4V!S^0<3|#@=5L ziN>^bb~B~h{(hyivS4}Z9u{DXaXZ+rM6tQEfAn0WIyK&l!-L5a5cLZvfg;RNL=FK7 zCleA;0lDrY;c>cwM2|b%y+G$U>Vxg^p0$_29tmH#o$oOL|79CxHDCITa-T0>xDYo0 z-#Z`h9q^&`9r8Y$?{4ewm0U<>jy3;Siy!sc?9IZO((EV~@2}@f3!vlPv(4R|tz>V1 zYkM~dH}|#=QVi#OJ&bMSDTp$);22Vpa6Wi+n?qeAV3DS|l6`Qo>hJjJbssp5lF zW>vf;)QK*ucv9&^O&I`=4S=uW-RvkDd) z+V&!XyN?f_frv6OtP8KLI#GSJW$iJ z&UtVof^=FcQ3S`boCWhQ0#@)K(?O;JE{<89>p+*rEi4PM4vh8;11I9Nb+e+Xb?e&i zjfw05_||{M@>*<5S;}iq8Y1M-sF6_v#;8Lmv13kY}XqHxLBc;QQJ?F0=g(kT0cpk?sabLL6W?v zS>El~Hjt+ITME2DH4|18rM3@&0`KKCXG%dUPD|d=W|)-b4oGU$Cq{j0)Gd|PGTkA~ zeKDdQg7WbyA=sxNL*l@j@QRQT_OSPAwDk7e#)acv>*>=fD<&3Um(OI5R=exH-u)$| beRZX25F7blP}=@RN5MDdEwaU3B?I~|fgE;Z literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/__init__.py b/lib/openai/api_resources/abstract/__init__.py new file mode 100644 index 0000000..32830e2 --- /dev/null +++ b/lib/openai/api_resources/abstract/__init__.py @@ -0,0 +1,10 @@ +# flake8: noqa + +from openai.api_resources.abstract.api_resource import APIResource +from openai.api_resources.abstract.createable_api_resource import CreateableAPIResource +from openai.api_resources.abstract.deletable_api_resource import DeletableAPIResource +from openai.api_resources.abstract.listable_api_resource import ListableAPIResource +from openai.api_resources.abstract.nested_resource_class_methods import ( + nested_resource_class_methods, +) +from openai.api_resources.abstract.updateable_api_resource import UpdateableAPIResource diff --git a/lib/openai/api_resources/abstract/__pycache__/__init__.cpython-39.pyc b/lib/openai/api_resources/abstract/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8cb53555ba2334c3a8d1a14168dab721bf7738d GIT binary patch literal 771 zcma))&r8EF6vxwbUArHi)PtaZzyvq&$3;by?J^i5j$KM2WX)qMOV*N?9sH9#_z&&s z$$!CBadd`&}^J=M-b_Rj$?I2 zRHPfaxpqZWdZCx=s;Egn^mFZrK-R;0u4|$pn_<&ve?;2Bm&r)l&Z_zJGI#|{mnsHx zJnkuA8rVcY@yAgg1nB+v;V{8J!evEQFnl7SMi(ZwZV(m+j^elN-PL^7y zYX4|?x%IYK-8bwzQGxm7+jrc%jm8KHqfxAqEb0T!b(%%fOjDdO4l2^-A}^z1GKnzJ uaFMZi&ZdCTZx7vFOR>ATr7oq(BGHs~vbR-mFH^o0@L(NB`{G;9db&Rp4Eb~b literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/__pycache__/api_resource.cpython-39.pyc b/lib/openai/api_resources/abstract/__pycache__/api_resource.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20438716ccbe04836417c3ec7114918fc28e4091 GIT binary patch literal 3805 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 literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/__pycache__/createable_api_resource.cpython-39.pyc b/lib/openai/api_resources/abstract/__pycache__/createable_api_resource.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54e0304846aefdb1ffd34cc4c92a14102a746998 GIT binary patch literal 1952 zcma)7OK%%D5MJ(bUy>}i&Z`d6)Ck}L3J}}qG3X zSXf<}T>T&7qYwQh{ReyPsXw8~AsuohTL$c+6gbNn4(IuiVhalm3&!pr%UrMi(#-t8 zz&wMgG~pIUi8Vw74Q*l%9pWG@vlBdYiEGwQ;thS`BkMQ9=|-vMWheU4JR7=F+T|8Z^#({`4N!?nyL3uix*VOl6R))18~)7h*%N<)OTTai7^Zt*A6v2u z``(!~3AkNa1HTMPeBj)*CSm%>Xa+~N>`!&Vv|WVn8{8>D;|%?dO0NtLbilaV!BwDZ z*k?-As{$%iHMQ(mNEK&k)ym`{OXD#sfE(XlUsbE>dr){XRrq#QjqmNJYLw@hECgSH z)K|2bl~vVgN*gjHEmMwZpU5&j6b02jv>Ih8pE;AdP{iFN`!9A#U_Nwnn}kuql%gY< zkVQ5gNjL+Cb14quH^l$#`A(N^?n(_Z(%wyvSrT(_sGHqsR8Ewr%GU>u#EA?*Nrsx} zKFB7JD!hOYY3qpiMQjx|m2trHxg|dXVUaZF+l=jPu^MP)9|U_YU)ds#lB) z*((5!U-y2E`#lvG;z`b;BX%fMFUv*B;&mO0oZ?cgv%V@Mi;DH=0z1b2Ok*uSA@`|H zHfNA{t#t`{or-z{#0oL8XTOi0j}iV9;6)UmCDcF-yN$*h*TQq{X5*2@>1?!Mq4{56 zs%;>EFKdchxAe|jKx!}v%V2;wD#Ow*F}F`~&zUqP%?T>(fm1d>A9Mk;m^-KLo^_79 z70xqNzy)ng+dyLuTsu!#oYE}el(T|$+-ief8~@GWSN_zZbRgX7^C(LJvjr_OTDO8` z{eg%Ic^9_NTT*8z7c{v<$sNsJLdAn7u%VJ|EnWmt1<-TDgF zb&s0!JqiMhf=?}cDZ?Mf53bE|tu}ORKmOF#e+KgZ`mqad?N8Q!cSYU;a~)USgAWM+ zcIM`qD#~S$nS#mtdZq8gT*f}DBi#YBVE=7D-#i`TBa0&BBZ;EGtf;GZ<72lSttc^{33 Zxx$<14c>xp>N0$Xbo4rf+o*+F&c7-k@)iI9 literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/__pycache__/deletable_api_resource.cpython-39.pyc b/lib/openai/api_resources/abstract/__pycache__/deletable_api_resource.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d72d7b84f50a183bfe2ac5530602f7a7b8ad41e7 GIT binary patch literal 1772 zcmb_cOK%)S5bo}I?!y}s0w#jP3bBZdl(pc90%R3i$O0`^f+I9qsnt8(cE+Ba8F%+M z@oH8=*k^u04za|=2ma7pIq??|lBw=pTejsRy{g)->PLP3Rn@SSl_r9*_2)zWi;vJ> z#<)IQFrI>_ZBQI>oT4d?FgA0NlBp9pcJ8F^)QdbjcT;~FM1h@qX*g{}4UE1=+~>h5 z;(-WGoT$me7pUL(8*W8?TsIHPTnk#Hr2?xle9aQgCaD0!6H?|9CavN2)^nlqQpUy~ z42$IF<3hyditEDz<0*)G0!p9=a}*Kb@CGN`ImIV9a=~hHmwVRoz-kGf`)@H1czEi; zj(}r;*RCIIh*Vf4*Mg5?!^-6dVPZl)15v+%(x^fjpAdOp6As~i!xehxoT0g^-3siv z2ewy{$~hxrGWX|B`}@%9QF(g?^RuZkE3u z%+}U>s;5AG$BN|fk&r6Mvu}E8=2`D-Z6{M@QRGs?9l%Cc1Nq&a>aWzzL?xNhEQ^IS zRMgFvEG@;RNnF&hp=c>nNnkB{NcV)M5QCZvD9boq29r>LDDsBU&hyPEw10HC5v{UW zDPbd=D5Tg;-bBGq+nXag+>#~_QD-YVVrjwwlc|S(SUYj5YDXo!4lm=>Ei+pNs@(}w z0?Ocx&Z3Ti!LI<-4j?^8auu%r=kVFr;~fQ5j9N8M?G;v5m5Er`_p|gq!c0u6xZ|FC1hjj3)cUQp literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/__pycache__/engine_api_resource.cpython-39.pyc b/lib/openai/api_resources/abstract/__pycache__/engine_api_resource.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..baf4151bea4e66fb28d62223ae2b58abb6537697 GIT binary patch literal 5942 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 DAmf=v literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/__pycache__/listable_api_resource.cpython-39.pyc b/lib/openai/api_resources/abstract/__pycache__/listable_api_resource.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03a5c6941ce757c7aac4ab484a11430946ed1f71 GIT binary patch literal 2122 zcma)7OK%%D5GJ`#Nh{ZN499Mp!VT)f7KshySo9G$F&d+R5+rpG3jxGhq?MH=?S@<# zwXgz;#@F;WSU@j1^q2G>?6s%k=d3-aB>-8#uw)@ALID*k% z$jmkynETK*5OOou{g_2YJ#h9jR2RLR!|gpYA@BIvuE%1lHTmk{^#V*}}uK z%>(8>bp0I=Ng^sp#H1ytU?=1ijcgcKjT#g<2e_JWg?ECS3tS+4Q8An+EKsjr-guno zJl^lh84X$#q@Ohl2I!L~(DgEqf^6>>2LIujtK~&jgXag7i5)-@gPrm7Ppgr zne7x?TbFJYtGK7%5LE|ZZ?gwqHutnty7|1NlC1eq>cc$En(Zv-`b8|H zYUbdwd(Fqmep7?EyII^ijN4K-(@gf`WNYHsiSP7QysvW=xAHC21?}Rq>zmoJs=&4A zN6ml`HvD{!2R5(T&}g~1d)g2|DB=Nh{R@zsbZ9{fJ6kNsDI0@t3a5auymm$|%viv& zF>A7sH)4gC+Z_kGd%%u}`U%#(F&X)SfoJ`~FW7;#LPnMTZL_m-$W&*#r_x`@tEV*q z@>a;0{z?nCs2nixzugG!(nnMGV=c=Xj*h^qN!p)G=K!ZNNL4%TC&M_0`Qh@`hThQM z!q&UftzU2G;my6i9%Nanaw*mU6zh0$eM5(VLWf0x0m>A|>I0lw<87H6KjU5=_r=-W z3cVIN+tJ>$oya#ozWp$&w7Rj@e4x6~VmutENsuxn50alF@5$4h2H)OQh`MNDw|^9O z69KnkMub&`&M!SQ@K7GBMc~vD5>!Qfh>Ml;Jc|}kBXO=@VH|Kz~hf9h2Noq7L~#)m|(B%rZuXLY9pF609kdie{7EeSP!^e z1kcGEt5I4IA1Eu;sb6>^LZ`gL=O}xfuBA6UtFRuFtF5#Tn8`V0`Xmi`y5Es4$bN8K zt`d19p#+#jubYUe8*omoA(<0072pC^7qtSU^p(_E+6N?49@Q`1a3yZahy5XR{V5Pq z!w^lscRqy3e;-&#ZOf&@)j1Y#VzlRm;!l*?0P_Dru?sK5Bl6z?Q8z(k=%|n27x{oU zlS{_0>Jyk#w~(MUS4=G(NCchlz{<78E?pSAHqTfiA#@tpLf8B*{(k9m-j92d^Rmi$ zFBJo*pS%n>e>sS|)0LV@-k!`~q{0+aV~L4_`s}jS2x^7dr4ZU!3-mo8K18T$F^kaY z_cpjS!%^~2@bP8doNt7c$em!pl%k2tOqKXzGQEja0ia{vQN4Ng7F?QE4rmb7rU&pD dHbu(ZFyruZ)l2`Yi6g$jS5c!8%QT>Y^)DmT8XEur literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/__pycache__/nested_resource_class_methods.cpython-39.pyc b/lib/openai/api_resources/abstract/__pycache__/nested_resource_class_methods.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..476e0db1c2684c07b81298f36d7dd8350f8013c5 GIT binary patch literal 4061 zcmb^!YmXCE^ggE3>9pNl7Fd?2ixH(Ntl|SjLNp+egj57ErdFF_?ktq;w&mU_kI6J9 zF3FY;7-IYZkoct^{1^U;G4YF^`~xPwvYvD2Znq2z>dI~B%zd15?|I)dUb*ZNXlK6P zDSp~Y$j>+!Zx#&Rg-4wLAc&wXvP5f?VxP6x60h+kt7gHtD00HSO=~$(5_#c3&lY7- z5H9rcf?pt&iCIMz_^d6_%8}ZnGhSYrQ~)9hq3Yu-U4f_H&4AnhkFp zb9mG#00T>q9+6Xm4)_eYV-1)d?-^Kk*nsN(%OP;MOuwQlWQDG<6~1CE_`O_>~^6~ z1s~*q6B?433}{GWx=fcj=;aw3YGid|wiP?W`#XH=!eF zOzx8hpiERes1%47z%}|jM%SS7NX!L;RZ#H^Ig0b>lDrx@2h_a&M;zW2Rx9`|PpL*v zwj_g{D^|k&mP%MtB!xEkXdoKU<**K}Y<0cx%8>ki^W?GmMHNU@zu?Pew|**6i(#i* zzuXNQ>WU`TOHqPsS}59NUsaw zaCkJ4rGxFGJcJD@$j{>$rWN6h0rD9<>MZ~vxk_Um=VDvXyS(qj&Q%tJ1>!uoDZ6Xc zdB4zi`#j|Lt+rF650Mp$&c+~R%Fj)=gGWhW_Byv3>%M7v) zUtmoVv+wBF6g-$!Av1JTIHXDbau9l9D2JAIHHjf2XAq#X4Owl+D7FnJjno-yTSp5J zYG!iGzh+W?vsyV1yDH?F&^|0fo*V%4TpqcqlbvJdIR# z5C-cigXGKl)|k>Ptu(%_G!Dv=7$C#kn6@y>tgB4!$D#Hp9cQy^Y-B~%i73?sWWde4 z&*S!wilY_KhTZE5bf58SYSX%qdr(GHgha2)iafK_5zU2LS%C$h8qYQ&yGI2x zkHU(e6+A6rE37JCe6qahaannnP~H*%0#}?vtyPP%)&C*)#-~IDGY@iGSRX!(3?ZKz z2|S0rj8AXJbF$*9n}E3<+STm(zYgsUH;?jq`hbeIUu-Y7J2%>Mqq_Lm98^ZMTCu9o z4CF~(rI{@$>RKYrT8aCK*XjlDOWBc?$ux-3L~xr@(Z2Fo9G6W>F?a9_I5#;8 zEi+run8pLm+)H}SSjWsA!nvXrC7l`O65CU^+I}N(M+Yle06cwt>w+tR^GtySUnSCO zr3TUYC2vwAUS#T10FYR1D5`8i_3t-#cGTPEP-cGaD*MZFTvj|B>5Wl-avqNsl?hi&6TfWPYbl~$;aRYrLgoNc?Y5j z;tC<#)|E$L&esVQSD|4alk-V<^tFw-U6*(qzl-kTR~E0H9zyvI81TX!;3C|J-AswF zH(Mb*RIje!fygMa8Fg3XJJ8z%XCF(fQ{~$Tjv>e}vG0+};UO-*0g`bpj>A|-$7zrn z0!0HSDZ%yKYF;`F`N VVpBQedw;lJf;rG5|8&dg`)~i2CrAJQ literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/__pycache__/updateable_api_resource.cpython-39.pyc b/lib/openai/api_resources/abstract/__pycache__/updateable_api_resource.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..85a75e88cb99e97c8a67946f6945bb50a0c9e86a GIT binary patch literal 1014 zcmb_ZO=}cE5bckd+0ABSL^L4iG2jN=1y3R(1_cR95JN7*K+`+jNyg0Vbh>+x$a+f7 z{sIpQUUKjc_0^MqAt$S5VzPl8E$E`Fy1HJ~dvaxEK)|+s-B)Xjklz?AS_HvMIPNJp zMHDSbMN>-g&PrDCl(%HKv5rr8ny6%ljCz0IHyP0|IBIIIg(({c z(bDE8S$H`uH3Z9>@3%f^S2s4-IhHTltUZvEtuo3m!q{ z0yx~;HA;GoAd3L7>!o3HJa6H+N8l8llkemQ@pMj4=pI`mU-+DSWQxuC9xKS4{iI(h z?8Udw<8?P#cOf#h^CKRX@=`h{8e3X~NLPjP(igd~`l!*)pGAS^rAxUhR7i}pvT}h# z`5Cb*@ZsFPdY0`vZC$pL+rnh8wLA2+$@YyG?m#MSGY@^9WbcY;<_fPL8<`)b#Y?pEQ>|h{cMrISxrxt77lCkNYDLgx#f|AKXaXxGM#{603b< zXZinP(k1{LVk3QiFya=6=_tG{+U-#<^o5wo zN(&JNLO_eA#QRW)qehk&FUzf{mG%di|H7|2c40=koaR$%=X*1=|-pHcu^%c{-@)$v_-Yr#y8Co`p>(CUQiqc`5n?B G8}h$Yy8smc literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/abstract/api_resource.py b/lib/openai/api_resources/abstract/api_resource.py new file mode 100644 index 0000000..5d54bb9 --- /dev/null +++ b/lib/openai/api_resources/abstract/api_resource.py @@ -0,0 +1,172 @@ +from urllib.parse import quote_plus + +import openai +from openai import api_requestor, error, util +from openai.openai_object import OpenAIObject +from openai.util import ApiType +from typing import Optional + + +class APIResource(OpenAIObject): + api_prefix = "" + azure_api_prefix = "openai" + azure_deployments_prefix = "deployments" + + @classmethod + def retrieve( + cls, id, api_key=None, request_id=None, request_timeout=None, **params + ): + instance = cls(id=id, api_key=api_key, **params) + instance.refresh(request_id=request_id, request_timeout=request_timeout) + return instance + + @classmethod + def aretrieve( + cls, id, api_key=None, request_id=None, request_timeout=None, **params + ): + instance = cls(id=id, api_key=api_key, **params) + return instance.arefresh(request_id=request_id, request_timeout=request_timeout) + + def refresh(self, request_id=None, request_timeout=None): + self.refresh_from( + self.request( + "get", + self.instance_url(), + request_id=request_id, + request_timeout=request_timeout, + ) + ) + return self + + async def arefresh(self, request_id=None, request_timeout=None): + self.refresh_from( + await self.arequest( + "get", + self.instance_url(operation="refresh"), + request_id=request_id, + request_timeout=request_timeout, + ) + ) + return self + + @classmethod + def class_url(cls): + if cls == APIResource: + raise NotImplementedError( + "APIResource is an abstract class. You should perform actions on its subclasses." + ) + # Namespaces are separated in object names with periods (.) and in URLs + # with forward slashes (/), so replace the former with the latter. + base = cls.OBJECT_NAME.replace(".", "/") # type: ignore + if cls.api_prefix: + return "/%s/%s" % (cls.api_prefix, base) + return "/%s" % (base) + + def instance_url(self, operation=None): + id = self.get("id") + + if not isinstance(id, str): + raise error.InvalidRequestError( + "Could not determine which URL to request: %s instance " + "has invalid ID: %r, %s. ID should be of type `str` (or" + " `unicode`)" % (type(self).__name__, id, type(id)), + "id", + ) + api_version = self.api_version or openai.api_version + extn = quote_plus(id) + + if self.typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD): + if not api_version: + raise error.InvalidRequestError( + "An API version is required for the Azure API type." + ) + + if not operation: + base = self.class_url() + return "/%s%s/%s?api-version=%s" % ( + self.azure_api_prefix, + base, + extn, + api_version, + ) + + return "/%s/%s/%s/%s?api-version=%s" % ( + self.azure_api_prefix, + self.azure_deployments_prefix, + extn, + operation, + api_version, + ) + + elif self.typed_api_type == ApiType.OPEN_AI: + base = self.class_url() + return "%s/%s" % (base, extn) + + else: + raise error.InvalidAPIType("Unsupported API type %s" % self.api_type) + + # The `method_` and `url_` arguments are suffixed with an underscore to + # avoid conflicting with actual request parameters in `params`. + @classmethod + def _static_request( + cls, + method_, + url_, + api_key=None, + api_base=None, + api_type=None, + request_id=None, + api_version=None, + organization=None, + **params, + ): + requestor = api_requestor.APIRequestor( + api_key, + api_version=api_version, + organization=organization, + api_base=api_base, + api_type=api_type, + ) + response, _, api_key = requestor.request( + method_, url_, params, request_id=request_id + ) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + @classmethod + async def _astatic_request( + cls, + method_, + url_, + api_key=None, + api_base=None, + api_type=None, + request_id=None, + api_version=None, + organization=None, + **params, + ): + requestor = api_requestor.APIRequestor( + api_key, + api_version=api_version, + organization=organization, + api_base=api_base, + api_type=api_type, + ) + response, _, api_key = await requestor.arequest( + method_, url_, params, request_id=request_id + ) + return response + + @classmethod + def _get_api_type_and_version( + cls, api_type: Optional[str] = None, api_version: Optional[str] = None + ): + typed_api_type = ( + ApiType.from_str(api_type) + if api_type + else ApiType.from_str(openai.api_type) + ) + typed_api_version = api_version or openai.api_version + return (typed_api_type, typed_api_version) diff --git a/lib/openai/api_resources/abstract/createable_api_resource.py b/lib/openai/api_resources/abstract/createable_api_resource.py new file mode 100644 index 0000000..1361c02 --- /dev/null +++ b/lib/openai/api_resources/abstract/createable_api_resource.py @@ -0,0 +1,98 @@ +from openai import api_requestor, util, error +from openai.api_resources.abstract.api_resource import APIResource +from openai.util import ApiType + + +class CreateableAPIResource(APIResource): + plain_old_data = False + + @classmethod + def __prepare_create_requestor( + cls, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + ): + requestor = api_requestor.APIRequestor( + api_key, + api_base=api_base, + api_type=api_type, + api_version=api_version, + organization=organization, + ) + typed_api_type, api_version = cls._get_api_type_and_version( + api_type, api_version + ) + + if typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD): + base = cls.class_url() + url = "/%s%s?api-version=%s" % (cls.azure_api_prefix, base, api_version) + elif typed_api_type == ApiType.OPEN_AI: + url = cls.class_url() + else: + raise error.InvalidAPIType("Unsupported API type %s" % api_type) + return requestor, url + + @classmethod + def create( + cls, + api_key=None, + api_base=None, + api_type=None, + request_id=None, + api_version=None, + organization=None, + **params, + ): + requestor, url = cls.__prepare_create_requestor( + api_key, + api_base, + api_type, + api_version, + organization, + ) + + response, _, api_key = requestor.request( + "post", url, params, request_id=request_id + ) + + return util.convert_to_openai_object( + response, + api_key, + api_version, + organization, + plain_old_data=cls.plain_old_data, + ) + + @classmethod + async def acreate( + cls, + api_key=None, + api_base=None, + api_type=None, + request_id=None, + api_version=None, + organization=None, + **params, + ): + requestor, url = cls.__prepare_create_requestor( + api_key, + api_base, + api_type, + api_version, + organization, + ) + + response, _, api_key = await requestor.arequest( + "post", url, params, request_id=request_id + ) + + return util.convert_to_openai_object( + response, + api_key, + api_version, + organization, + plain_old_data=cls.plain_old_data, + ) diff --git a/lib/openai/api_resources/abstract/deletable_api_resource.py b/lib/openai/api_resources/abstract/deletable_api_resource.py new file mode 100644 index 0000000..a800ceb --- /dev/null +++ b/lib/openai/api_resources/abstract/deletable_api_resource.py @@ -0,0 +1,48 @@ +from urllib.parse import quote_plus +from typing import Awaitable + +from openai import error +from openai.api_resources.abstract.api_resource import APIResource +from openai.util import ApiType + + +class DeletableAPIResource(APIResource): + @classmethod + def __prepare_delete(cls, sid, api_type=None, api_version=None): + if isinstance(cls, APIResource): + raise ValueError(".delete may only be called as a class method now.") + + base = cls.class_url() + extn = quote_plus(sid) + + typed_api_type, api_version = cls._get_api_type_and_version( + api_type, api_version + ) + if typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD): + url = "/%s%s/%s?api-version=%s" % ( + cls.azure_api_prefix, + base, + extn, + api_version, + ) + elif typed_api_type == ApiType.OPEN_AI: + url = "%s/%s" % (base, extn) + else: + raise error.InvalidAPIType("Unsupported API type %s" % api_type) + return url + + @classmethod + def delete(cls, sid, api_type=None, api_version=None, **params): + url = cls.__prepare_delete(sid, api_type, api_version) + + return cls._static_request( + "delete", url, api_type=api_type, api_version=api_version, **params + ) + + @classmethod + def adelete(cls, sid, api_type=None, api_version=None, **params) -> Awaitable: + url = cls.__prepare_delete(sid, api_type, api_version) + + return cls._astatic_request( + "delete", url, api_type=api_type, api_version=api_version, **params + ) diff --git a/lib/openai/api_resources/abstract/engine_api_resource.py b/lib/openai/api_resources/abstract/engine_api_resource.py new file mode 100644 index 0000000..1f172d8 --- /dev/null +++ b/lib/openai/api_resources/abstract/engine_api_resource.py @@ -0,0 +1,325 @@ +import time +from pydoc import apropos +from typing import Optional +from urllib.parse import quote_plus + +import openai +from openai import api_requestor, error, util +from openai.api_resources.abstract.api_resource import APIResource +from openai.openai_response import OpenAIResponse +from openai.util import ApiType + +MAX_TIMEOUT = 20 + + +class EngineAPIResource(APIResource): + plain_old_data = False + + def __init__(self, engine: Optional[str] = None, **kwargs): + super().__init__(engine=engine, **kwargs) + + @classmethod + def class_url( + cls, + engine: Optional[str] = None, + api_type: Optional[str] = None, + api_version: Optional[str] = None, + ): + # Namespaces are separated in object names with periods (.) and in URLs + # with forward slashes (/), so replace the former with the latter. + base = cls.OBJECT_NAME.replace(".", "/") # type: ignore + typed_api_type, api_version = cls._get_api_type_and_version( + api_type, api_version + ) + + if typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD): + if not api_version: + raise error.InvalidRequestError( + "An API version is required for the Azure API type." + ) + if engine is None: + raise error.InvalidRequestError( + "You must provide the deployment name in the 'engine' parameter to access the Azure OpenAI service" + ) + extn = quote_plus(engine) + return "/%s/%s/%s/%s?api-version=%s" % ( + cls.azure_api_prefix, + cls.azure_deployments_prefix, + extn, + base, + api_version, + ) + + elif typed_api_type == ApiType.OPEN_AI: + if engine is None: + return "/%s" % (base) + + extn = quote_plus(engine) + return "/engines/%s/%s" % (extn, base) + + else: + raise error.InvalidAPIType("Unsupported API type %s" % api_type) + + @classmethod + def __prepare_create_request( + cls, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + deployment_id = params.pop("deployment_id", None) + engine = params.pop("engine", deployment_id) + model = params.get("model", None) + timeout = params.pop("timeout", None) + stream = params.get("stream", False) + headers = params.pop("headers", None) + request_timeout = params.pop("request_timeout", None) + typed_api_type = cls._get_api_type_and_version(api_type=api_type)[0] + if typed_api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + if deployment_id is None and engine is None: + raise error.InvalidRequestError( + "Must provide an 'engine' or 'deployment_id' parameter to create a %s" + % cls, + "engine", + ) + else: + if model is None and engine is None: + raise error.InvalidRequestError( + "Must provide an 'engine' or 'model' parameter to create a %s" + % cls, + "engine", + ) + + if timeout is None: + # No special timeout handling + pass + elif timeout > 0: + # API only supports timeouts up to MAX_TIMEOUT + params["timeout"] = min(timeout, MAX_TIMEOUT) + timeout = (timeout - params["timeout"]) or None + elif timeout == 0: + params["timeout"] = MAX_TIMEOUT + + requestor = api_requestor.APIRequestor( + api_key, + api_base=api_base, + api_type=api_type, + api_version=api_version, + organization=organization, + ) + url = cls.class_url(engine, api_type, api_version) + return ( + deployment_id, + engine, + timeout, + stream, + headers, + request_timeout, + typed_api_type, + requestor, + url, + params, + ) + + @classmethod + def create( + cls, + api_key=None, + api_base=None, + api_type=None, + request_id=None, + api_version=None, + organization=None, + **params, + ): + ( + deployment_id, + engine, + timeout, + stream, + headers, + request_timeout, + typed_api_type, + requestor, + url, + params, + ) = cls.__prepare_create_request( + api_key, api_base, api_type, api_version, organization, **params + ) + + response, _, api_key = requestor.request( + "post", + url, + params=params, + headers=headers, + stream=stream, + request_id=request_id, + request_timeout=request_timeout, + ) + + if stream: + # must be an iterator + assert not isinstance(response, OpenAIResponse) + return ( + util.convert_to_openai_object( + line, + api_key, + api_version, + organization, + engine=engine, + plain_old_data=cls.plain_old_data, + ) + for line in response + ) + else: + obj = util.convert_to_openai_object( + response, + api_key, + api_version, + organization, + engine=engine, + plain_old_data=cls.plain_old_data, + ) + + if timeout is not None: + obj.wait(timeout=timeout or None) + + return obj + + @classmethod + async def acreate( + cls, + api_key=None, + api_base=None, + api_type=None, + request_id=None, + api_version=None, + organization=None, + **params, + ): + ( + deployment_id, + engine, + timeout, + stream, + headers, + request_timeout, + typed_api_type, + requestor, + url, + params, + ) = cls.__prepare_create_request( + api_key, api_base, api_type, api_version, organization, **params + ) + response, _, api_key = await requestor.arequest( + "post", + url, + params=params, + headers=headers, + stream=stream, + request_id=request_id, + request_timeout=request_timeout, + ) + + if stream: + # must be an iterator + assert not isinstance(response, OpenAIResponse) + return ( + util.convert_to_openai_object( + line, + api_key, + api_version, + organization, + engine=engine, + plain_old_data=cls.plain_old_data, + ) + async for line in response + ) + else: + obj = util.convert_to_openai_object( + response, + api_key, + api_version, + organization, + engine=engine, + plain_old_data=cls.plain_old_data, + ) + + if timeout is not None: + await obj.await_(timeout=timeout or None) + + return obj + + def instance_url(self): + id = self.get("id") + + if not isinstance(id, str): + raise error.InvalidRequestError( + f"Could not determine which URL to request: {type(self).__name__} instance has invalid ID: {id}, {type(id)}. ID should be of type str.", + "id", + ) + + extn = quote_plus(id) + params_connector = "?" + + if self.typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD): + api_version = self.api_version or openai.api_version + if not api_version: + raise error.InvalidRequestError( + "An API version is required for the Azure API type." + ) + base = self.OBJECT_NAME.replace(".", "/") + url = "/%s/%s/%s/%s/%s?api-version=%s" % ( + self.azure_api_prefix, + self.azure_deployments_prefix, + self.engine, + base, + extn, + api_version, + ) + params_connector = "&" + + elif self.typed_api_type == ApiType.OPEN_AI: + base = self.class_url(self.engine, self.api_type, self.api_version) + url = "%s/%s" % (base, extn) + + else: + raise error.InvalidAPIType("Unsupported API type %s" % self.api_type) + + timeout = self.get("timeout") + if timeout is not None: + timeout = quote_plus(str(timeout)) + url += params_connector + "timeout={}".format(timeout) + return url + + def wait(self, timeout=None): + start = time.time() + while self.status != "complete": + self.timeout = ( + min(timeout + start - time.time(), MAX_TIMEOUT) + if timeout is not None + else MAX_TIMEOUT + ) + if self.timeout < 0: + del self.timeout + break + self.refresh() + return self + + async def await_(self, timeout=None): + """Async version of `EngineApiResource.wait`""" + start = time.time() + while self.status != "complete": + self.timeout = ( + min(timeout + start - time.time(), MAX_TIMEOUT) + if timeout is not None + else MAX_TIMEOUT + ) + if self.timeout < 0: + del self.timeout + break + await self.arefresh() + return self diff --git a/lib/openai/api_resources/abstract/listable_api_resource.py b/lib/openai/api_resources/abstract/listable_api_resource.py new file mode 100644 index 0000000..3e59979 --- /dev/null +++ b/lib/openai/api_resources/abstract/listable_api_resource.py @@ -0,0 +1,95 @@ +from openai import api_requestor, util, error +from openai.api_resources.abstract.api_resource import APIResource +from openai.util import ApiType + + +class ListableAPIResource(APIResource): + @classmethod + def auto_paging_iter(cls, *args, **params): + return cls.list(*args, **params).auto_paging_iter() + + @classmethod + def __prepare_list_requestor( + cls, + api_key=None, + api_version=None, + organization=None, + api_base=None, + api_type=None, + ): + requestor = api_requestor.APIRequestor( + api_key, + api_base=api_base or cls.api_base(), + api_version=api_version, + api_type=api_type, + organization=organization, + ) + + typed_api_type, api_version = cls._get_api_type_and_version( + api_type, api_version + ) + + if typed_api_type in (ApiType.AZURE, ApiType.AZURE_AD): + base = cls.class_url() + url = "/%s%s?api-version=%s" % (cls.azure_api_prefix, base, api_version) + elif typed_api_type == ApiType.OPEN_AI: + url = cls.class_url() + else: + raise error.InvalidAPIType("Unsupported API type %s" % api_type) + return requestor, url + + @classmethod + def list( + cls, + api_key=None, + request_id=None, + api_version=None, + organization=None, + api_base=None, + api_type=None, + **params, + ): + requestor, url = cls.__prepare_list_requestor( + api_key, + api_version, + organization, + api_base, + api_type, + ) + + response, _, api_key = requestor.request( + "get", url, params, request_id=request_id + ) + openai_object = util.convert_to_openai_object( + response, api_key, api_version, organization + ) + openai_object._retrieve_params = params + return openai_object + + @classmethod + async def alist( + cls, + api_key=None, + request_id=None, + api_version=None, + organization=None, + api_base=None, + api_type=None, + **params, + ): + requestor, url = cls.__prepare_list_requestor( + api_key, + api_version, + organization, + api_base, + api_type, + ) + + response, _, api_key = await requestor.arequest( + "get", url, params, request_id=request_id + ) + openai_object = util.convert_to_openai_object( + response, api_key, api_version, organization + ) + openai_object._retrieve_params = params + return openai_object diff --git a/lib/openai/api_resources/abstract/nested_resource_class_methods.py b/lib/openai/api_resources/abstract/nested_resource_class_methods.py new file mode 100644 index 0000000..bfa5bcd --- /dev/null +++ b/lib/openai/api_resources/abstract/nested_resource_class_methods.py @@ -0,0 +1,154 @@ +from urllib.parse import quote_plus + +from openai import api_requestor, util + + +def _nested_resource_class_methods( + resource, + path=None, + operations=None, + resource_plural=None, + async_=False, +): + if resource_plural is None: + resource_plural = "%ss" % resource + if path is None: + path = resource_plural + if operations is None: + raise ValueError("operations list required") + + def wrapper(cls): + def nested_resource_url(cls, id, nested_id=None): + url = "%s/%s/%s" % (cls.class_url(), quote_plus(id), quote_plus(path)) + if nested_id is not None: + url += "/%s" % quote_plus(nested_id) + return url + + resource_url_method = "%ss_url" % resource + setattr(cls, resource_url_method, classmethod(nested_resource_url)) + + def nested_resource_request( + cls, + method, + url, + api_key=None, + request_id=None, + api_version=None, + organization=None, + **params, + ): + requestor = api_requestor.APIRequestor( + api_key, api_version=api_version, organization=organization + ) + response, _, api_key = requestor.request( + method, url, params, request_id=request_id + ) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + async def anested_resource_request( + cls, + method, + url, + api_key=None, + request_id=None, + api_version=None, + organization=None, + **params, + ): + requestor = api_requestor.APIRequestor( + api_key, api_version=api_version, organization=organization + ) + response, _, api_key = await requestor.arequest( + method, url, params, request_id=request_id + ) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + resource_request_method = "%ss_request" % resource + setattr( + cls, + resource_request_method, + classmethod( + anested_resource_request if async_ else nested_resource_request + ), + ) + + for operation in operations: + if operation == "create": + + def create_nested_resource(cls, id, **params): + url = getattr(cls, resource_url_method)(id) + return getattr(cls, resource_request_method)("post", url, **params) + + create_method = "create_%s" % resource + setattr(cls, create_method, classmethod(create_nested_resource)) + + elif operation == "retrieve": + + def retrieve_nested_resource(cls, id, nested_id, **params): + url = getattr(cls, resource_url_method)(id, nested_id) + return getattr(cls, resource_request_method)("get", url, **params) + + retrieve_method = "retrieve_%s" % resource + setattr(cls, retrieve_method, classmethod(retrieve_nested_resource)) + + elif operation == "update": + + def modify_nested_resource(cls, id, nested_id, **params): + url = getattr(cls, resource_url_method)(id, nested_id) + return getattr(cls, resource_request_method)("post", url, **params) + + modify_method = "modify_%s" % resource + setattr(cls, modify_method, classmethod(modify_nested_resource)) + + elif operation == "delete": + + def delete_nested_resource(cls, id, nested_id, **params): + url = getattr(cls, resource_url_method)(id, nested_id) + return getattr(cls, resource_request_method)( + "delete", url, **params + ) + + delete_method = "delete_%s" % resource + setattr(cls, delete_method, classmethod(delete_nested_resource)) + + elif operation == "list": + + def list_nested_resources(cls, id, **params): + url = getattr(cls, resource_url_method)(id) + return getattr(cls, resource_request_method)("get", url, **params) + + list_method = "list_%s" % resource_plural + setattr(cls, list_method, classmethod(list_nested_resources)) + + else: + raise ValueError("Unknown operation: %s" % operation) + + return cls + + return wrapper + + +def nested_resource_class_methods( + resource, + path=None, + operations=None, + resource_plural=None, +): + return _nested_resource_class_methods( + resource, path, operations, resource_plural, async_=False + ) + + +def anested_resource_class_methods( + resource, + path=None, + operations=None, + resource_plural=None, +): + return _nested_resource_class_methods( + resource, path, operations, resource_plural, async_=True + ) diff --git a/lib/openai/api_resources/abstract/updateable_api_resource.py b/lib/openai/api_resources/abstract/updateable_api_resource.py new file mode 100644 index 0000000..245f9b8 --- /dev/null +++ b/lib/openai/api_resources/abstract/updateable_api_resource.py @@ -0,0 +1,16 @@ +from urllib.parse import quote_plus +from typing import Awaitable + +from openai.api_resources.abstract.api_resource import APIResource + + +class UpdateableAPIResource(APIResource): + @classmethod + def modify(cls, sid, **params): + url = "%s/%s" % (cls.class_url(), quote_plus(sid)) + return cls._static_request("post", url, **params) + + @classmethod + def amodify(cls, sid, **params) -> Awaitable: + url = "%s/%s" % (cls.class_url(), quote_plus(sid)) + return cls._astatic_request("patch", url, **params) diff --git a/lib/openai/api_resources/audio.py b/lib/openai/api_resources/audio.py new file mode 100644 index 0000000..8ad6705 --- /dev/null +++ b/lib/openai/api_resources/audio.py @@ -0,0 +1,205 @@ +from typing import Any, List + +import openai +from openai import api_requestor, util +from openai.api_resources.abstract import APIResource + + +class Audio(APIResource): + OBJECT_NAME = "audio" + + @classmethod + def _get_url(cls, action): + return cls.class_url() + f"/{action}" + + @classmethod + def _prepare_request( + cls, + file, + filename, + model, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor = api_requestor.APIRequestor( + api_key, + api_base=api_base or openai.api_base, + api_type=api_type, + api_version=api_version, + organization=organization, + ) + files: List[Any] = [] + data = { + "model": model, + **params, + } + files.append(("file", (filename, file, "application/octet-stream"))) + return requestor, files, data + + @classmethod + def transcribe( + cls, + model, + file, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor, files, data = cls._prepare_request(file, file.name, model, **params) + url = cls._get_url("transcriptions") + response, _, api_key = requestor.request("post", url, files=files, params=data) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + @classmethod + def translate( + cls, + model, + file, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor, files, data = cls._prepare_request(file, file.name, model, **params) + url = cls._get_url("translations") + response, _, api_key = requestor.request("post", url, files=files, params=data) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + @classmethod + def transcribe_raw( + cls, + model, + file, + filename, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor, files, data = cls._prepare_request(file, filename, model, **params) + url = cls._get_url("transcriptions") + response, _, api_key = requestor.request("post", url, files=files, params=data) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + @classmethod + def translate_raw( + cls, + model, + file, + filename, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor, files, data = cls._prepare_request(file, filename, model, **params) + url = cls._get_url("translations") + response, _, api_key = requestor.request("post", url, files=files, params=data) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + @classmethod + async def atranscribe( + cls, + model, + file, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor, files, data = cls._prepare_request(file, file.name, model, **params) + url = cls._get_url("transcriptions") + response, _, api_key = await requestor.arequest( + "post", url, files=files, params=data + ) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + @classmethod + async def atranslate( + cls, + model, + file, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor, files, data = cls._prepare_request(file, file.name, model, **params) + url = cls._get_url("translations") + response, _, api_key = await requestor.arequest( + "post", url, files=files, params=data + ) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + @classmethod + async def atranscribe_raw( + cls, + model, + file, + filename, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor, files, data = cls._prepare_request(file, filename, model, **params) + url = cls._get_url("transcriptions") + response, _, api_key = await requestor.arequest( + "post", url, files=files, params=data + ) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) + + @classmethod + async def atranslate_raw( + cls, + model, + file, + filename, + api_key=None, + api_base=None, + api_type=None, + api_version=None, + organization=None, + **params, + ): + requestor, files, data = cls._prepare_request(file, filename, model, **params) + url = cls._get_url("translations") + response, _, api_key = await requestor.arequest( + "post", url, files=files, params=data + ) + return util.convert_to_openai_object( + response, api_key, api_version, organization + ) diff --git a/lib/openai/api_resources/chat_completion.py b/lib/openai/api_resources/chat_completion.py new file mode 100644 index 0000000..39fb58b --- /dev/null +++ b/lib/openai/api_resources/chat_completion.py @@ -0,0 +1,50 @@ +import time + +from openai import util +from openai.api_resources.abstract.engine_api_resource import EngineAPIResource +from openai.error import TryAgain + + +class ChatCompletion(EngineAPIResource): + engine_required = False + OBJECT_NAME = "chat.completions" + + @classmethod + def create(cls, *args, **kwargs): + """ + Creates a new chat completion for the provided messages and parameters. + + See https://platform.openai.com/docs/api-reference/chat-completions/create + for a list of valid parameters. + """ + start = time.time() + timeout = kwargs.pop("timeout", None) + + while True: + try: + return super().create(*args, **kwargs) + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) + + @classmethod + async def acreate(cls, *args, **kwargs): + """ + Creates a new chat completion for the provided messages and parameters. + + See https://platform.openai.com/docs/api-reference/chat-completions/create + for a list of valid parameters. + """ + start = time.time() + timeout = kwargs.pop("timeout", None) + + while True: + try: + return await super().acreate(*args, **kwargs) + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) diff --git a/lib/openai/api_resources/completion.py b/lib/openai/api_resources/completion.py new file mode 100644 index 0000000..7b9c44b --- /dev/null +++ b/lib/openai/api_resources/completion.py @@ -0,0 +1,50 @@ +import time + +from openai import util +from openai.api_resources.abstract import DeletableAPIResource, ListableAPIResource +from openai.api_resources.abstract.engine_api_resource import EngineAPIResource +from openai.error import TryAgain + + +class Completion(EngineAPIResource): + OBJECT_NAME = "completions" + + @classmethod + def create(cls, *args, **kwargs): + """ + Creates a new completion for the provided prompt and parameters. + + See https://platform.openai.com/docs/api-reference/completions/create for a list + of valid parameters. + """ + start = time.time() + timeout = kwargs.pop("timeout", None) + + while True: + try: + return super().create(*args, **kwargs) + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) + + @classmethod + async def acreate(cls, *args, **kwargs): + """ + Creates a new completion for the provided prompt and parameters. + + See https://platform.openai.com/docs/api-reference/completions/create for a list + of valid parameters. + """ + start = time.time() + timeout = kwargs.pop("timeout", None) + + while True: + try: + return await super().acreate(*args, **kwargs) + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) diff --git a/lib/openai/api_resources/customer.py b/lib/openai/api_resources/customer.py new file mode 100644 index 0000000..8690d07 --- /dev/null +++ b/lib/openai/api_resources/customer.py @@ -0,0 +1,17 @@ +from openai.openai_object import OpenAIObject + + +class Customer(OpenAIObject): + @classmethod + def get_url(cls, customer, endpoint): + return f"/customer/{customer}/{endpoint}" + + @classmethod + def create(cls, customer, endpoint, **params): + instance = cls() + return instance.request("post", cls.get_url(customer, endpoint), params) + + @classmethod + def acreate(cls, customer, endpoint, **params): + instance = cls() + return instance.arequest("post", cls.get_url(customer, endpoint), params) diff --git a/lib/openai/api_resources/deployment.py b/lib/openai/api_resources/deployment.py new file mode 100644 index 0000000..2f3fcd1 --- /dev/null +++ b/lib/openai/api_resources/deployment.py @@ -0,0 +1,119 @@ +from openai import util +from openai.api_resources.abstract import ( + DeletableAPIResource, + ListableAPIResource, + CreateableAPIResource, +) +from openai.error import InvalidRequestError, APIError + + +class Deployment(CreateableAPIResource, ListableAPIResource, DeletableAPIResource): + OBJECT_NAME = "deployments" + + @classmethod + def _check_create(cls, *args, **kwargs): + typed_api_type, _ = cls._get_api_type_and_version( + kwargs.get("api_type", None), None + ) + if typed_api_type not in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise APIError( + "Deployment operations are only available for the Azure API type." + ) + + if kwargs.get("model", None) is None: + raise InvalidRequestError( + "Must provide a 'model' parameter to create a Deployment.", + param="model", + ) + + scale_settings = kwargs.get("scale_settings", None) + if scale_settings is None: + raise InvalidRequestError( + "Must provide a 'scale_settings' parameter to create a Deployment.", + param="scale_settings", + ) + + if "scale_type" not in scale_settings or ( + scale_settings["scale_type"].lower() == "manual" + and "capacity" not in scale_settings + ): + raise InvalidRequestError( + "The 'scale_settings' parameter contains invalid or incomplete values.", + param="scale_settings", + ) + + @classmethod + def create(cls, *args, **kwargs): + """ + Creates a new deployment for the provided prompt and parameters. + """ + cls._check_create(*args, **kwargs) + return super().create(*args, **kwargs) + + @classmethod + def acreate(cls, *args, **kwargs): + """ + Creates a new deployment for the provided prompt and parameters. + """ + cls._check_create(*args, **kwargs) + return super().acreate(*args, **kwargs) + + @classmethod + def _check_list(cls, *args, **kwargs): + typed_api_type, _ = cls._get_api_type_and_version( + kwargs.get("api_type", None), None + ) + if typed_api_type not in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise APIError( + "Deployment operations are only available for the Azure API type." + ) + + @classmethod + def list(cls, *args, **kwargs): + cls._check_list(*args, **kwargs) + return super().list(*args, **kwargs) + + @classmethod + def alist(cls, *args, **kwargs): + cls._check_list(*args, **kwargs) + return super().alist(*args, **kwargs) + + @classmethod + def _check_delete(cls, *args, **kwargs): + typed_api_type, _ = cls._get_api_type_and_version( + kwargs.get("api_type", None), None + ) + if typed_api_type not in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise APIError( + "Deployment operations are only available for the Azure API type." + ) + + @classmethod + def delete(cls, *args, **kwargs): + cls._check_delete(*args, **kwargs) + return super().delete(*args, **kwargs) + + @classmethod + def adelete(cls, *args, **kwargs): + cls._check_delete(*args, **kwargs) + return super().adelete(*args, **kwargs) + + @classmethod + def _check_retrieve(cls, *args, **kwargs): + typed_api_type, _ = cls._get_api_type_and_version( + kwargs.get("api_type", None), None + ) + if typed_api_type not in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise APIError( + "Deployment operations are only available for the Azure API type." + ) + + @classmethod + def retrieve(cls, *args, **kwargs): + cls._check_retrieve(*args, **kwargs) + return super().retrieve(*args, **kwargs) + + @classmethod + def aretrieve(cls, *args, **kwargs): + cls._check_retrieve(*args, **kwargs) + return super().aretrieve(*args, **kwargs) diff --git a/lib/openai/api_resources/edit.py b/lib/openai/api_resources/edit.py new file mode 100644 index 0000000..985f062 --- /dev/null +++ b/lib/openai/api_resources/edit.py @@ -0,0 +1,57 @@ +import time + +from openai import util, error +from openai.api_resources.abstract.engine_api_resource import EngineAPIResource +from openai.error import TryAgain + + +class Edit(EngineAPIResource): + OBJECT_NAME = "edits" + + @classmethod + def create(cls, *args, **kwargs): + """ + Creates a new edit for the provided input, instruction, and parameters. + """ + start = time.time() + timeout = kwargs.pop("timeout", None) + + api_type = kwargs.pop("api_type", None) + typed_api_type = cls._get_api_type_and_version(api_type=api_type)[0] + if typed_api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise error.InvalidAPIType( + "This operation is not supported by the Azure OpenAI API yet." + ) + + while True: + try: + return super().create(*args, **kwargs) + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) + + @classmethod + async def acreate(cls, *args, **kwargs): + """ + Creates a new edit for the provided input, instruction, and parameters. + """ + start = time.time() + timeout = kwargs.pop("timeout", None) + + api_type = kwargs.pop("api_type", None) + typed_api_type = cls._get_api_type_and_version(api_type=api_type)[0] + if typed_api_type in (util.ApiType.AZURE, util.ApiType.AZURE_AD): + raise error.InvalidAPIType( + "This operation is not supported by the Azure OpenAI API yet." + ) + + while True: + try: + return await super().acreate(*args, **kwargs) + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) diff --git a/lib/openai/api_resources/embedding.py b/lib/openai/api_resources/embedding.py new file mode 100644 index 0000000..4eb97c6 --- /dev/null +++ b/lib/openai/api_resources/embedding.py @@ -0,0 +1,91 @@ +import base64 +import time + + +from openai import util +from openai.api_resources.abstract.engine_api_resource import EngineAPIResource +from openai.datalib import numpy as np, assert_has_numpy +from openai.error import TryAgain + + +class Embedding(EngineAPIResource): + OBJECT_NAME = "embeddings" + + @classmethod + def create(cls, *args, **kwargs): + """ + Creates a new embedding for the provided input and parameters. + + See https://platform.openai.com/docs/api-reference/embeddings for a list + of valid parameters. + """ + start = time.time() + timeout = kwargs.pop("timeout", None) + + user_provided_encoding_format = kwargs.get("encoding_format", None) + + # If encoding format was not explicitly specified, we opaquely use base64 for performance + if not user_provided_encoding_format: + kwargs["encoding_format"] = "base64" + + while True: + try: + response = super().create(*args, **kwargs) + + # If a user specifies base64, we'll just return the encoded string. + # This is only for the default case. + if not user_provided_encoding_format: + for data in response.data: + + # If an engine isn't using this optimization, don't do anything + if type(data["embedding"]) == str: + assert_has_numpy() + data["embedding"] = np.frombuffer( + base64.b64decode(data["embedding"]), dtype="float32" + ).tolist() + + return response + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) + + @classmethod + async def acreate(cls, *args, **kwargs): + """ + Creates a new embedding for the provided input and parameters. + + See https://platform.openai.com/docs/api-reference/embeddings for a list + of valid parameters. + """ + start = time.time() + timeout = kwargs.pop("timeout", None) + + user_provided_encoding_format = kwargs.get("encoding_format", None) + + # If encoding format was not explicitly specified, we opaquely use base64 for performance + if not user_provided_encoding_format: + kwargs["encoding_format"] = "base64" + + while True: + try: + response = await super().acreate(*args, **kwargs) + + # If a user specifies base64, we'll just return the encoded string. + # This is only for the default case. + if not user_provided_encoding_format: + for data in response.data: + + # If an engine isn't using this optimization, don't do anything + if type(data["embedding"]) == str: + data["embedding"] = np.frombuffer( + base64.b64decode(data["embedding"]), dtype="float32" + ).tolist() + + return response + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) diff --git a/lib/openai/api_resources/engine.py b/lib/openai/api_resources/engine.py new file mode 100644 index 0000000..5a0c467 --- /dev/null +++ b/lib/openai/api_resources/engine.py @@ -0,0 +1,50 @@ +import time +import warnings + +from openai import util +from openai.api_resources.abstract import ListableAPIResource, UpdateableAPIResource +from openai.error import TryAgain + + +class Engine(ListableAPIResource, UpdateableAPIResource): + OBJECT_NAME = "engines" + + def generate(self, timeout=None, **params): + start = time.time() + while True: + try: + return self.request( + "post", + self.instance_url() + "/generate", + params, + stream=params.get("stream"), + plain_old_data=True, + ) + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) + + async def agenerate(self, timeout=None, **params): + start = time.time() + while True: + try: + return await self.arequest( + "post", + self.instance_url() + "/generate", + params, + stream=params.get("stream"), + plain_old_data=True, + ) + except TryAgain as e: + if timeout is not None and time.time() > start + timeout: + raise + + util.log_info("Waiting for model to warm up", error=e) + + def embeddings(self, **params): + warnings.warn( + "Engine.embeddings is deprecated, use Embedding.create", DeprecationWarning + ) + return self.request("post", self.instance_url() + "/embeddings", params) diff --git a/lib/openai/api_resources/error_object.py b/lib/openai/api_resources/error_object.py new file mode 100644 index 0000000..555dc35 --- /dev/null +++ b/lib/openai/api_resources/error_object.py @@ -0,0 +1,28 @@ +from typing import Optional + +from openai.openai_object import OpenAIObject +from openai.util import merge_dicts + + +class ErrorObject(OpenAIObject): + def refresh_from( + self, + values, + api_key=None, + api_version=None, + api_type=None, + organization=None, + response_ms: Optional[int] = None, + ): + # Unlike most other API resources, the API will omit attributes in + # error objects when they have a null value. We manually set default + # values here to facilitate generic error handling. + values = merge_dicts({"message": None, "type": None}, values) + return super(ErrorObject, self).refresh_from( + values=values, + api_key=api_key, + api_version=api_version, + api_type=api_type, + organization=organization, + response_ms=response_ms, + ) diff --git a/lib/openai/api_resources/experimental/__init__.py b/lib/openai/api_resources/experimental/__init__.py new file mode 100644 index 0000000..d24c7b0 --- /dev/null +++ b/lib/openai/api_resources/experimental/__init__.py @@ -0,0 +1,3 @@ +from openai.api_resources.experimental.completion_config import ( # noqa: F401 + CompletionConfig, +) diff --git a/lib/openai/api_resources/experimental/__pycache__/__init__.cpython-39.pyc b/lib/openai/api_resources/experimental/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..294200c85ede084b211438e66a45c844ffefeaa9 GIT binary patch literal 286 zcmY+7F-rqM5QTRyjR=9X`3YBe2il29;A{l32{vJ2S$D_Fkh|SsX9N0^Ec`=ht*q^= zoJ5dh;JwEiczkX;Jy&w~AM>!5{M?u0tE4PMJQE8l=s}HorL_vxQq_|$`Kns)W?dR- zfPyJ@DZXI;SXZm7L=YV|j?iY{w9Ov4fmeczBgEo{#{bW(KkV`z_^$Fq;$T00cWs^+ zGMlB(NahZBQxch;isiKnkWB$Dm*xSV4P$`|a(?4_VDrasPx$RIzqJ-47Hb=N+nwEv NDQpM0)x!Q3sb3q~Rh$3- literal 0 HcmV?d00001 diff --git a/lib/openai/api_resources/experimental/__pycache__/completion_config.cpython-39.pyc b/lib/openai/api_resources/experimental/__pycache__/completion_config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac1327f4d6566b8b67d79bd76351fb354862584a GIT binary patch literal 562 zcmZWnOG^VW5KcC&)~Zl^96X5Ug0|pMM66p8MXUI@goTi9#u~bhn53Xy_0M?l5AD^H zf5D3;UD2uo^YP70^37xjpP#QIXNNBVrv}a>1^1v+h73ffJNsiqn7wDWL?T z10(`Lo{%7bfk5zBr4>=RK+Wo#yMdadSPnIC3%s8|=j`wtOg_>v6iZ!5!2ZoH9!v8# z8GE5gbAy&cH5>s%u|PBsm=Y1V#-wP3d8(Ya%(F1huI1oi4ek_ZnL=iH(u%*!EFRZQ zuURQ-jAc9p#)>*)X)Z>I=W~qRj(Ad5=96IO_T;?nfAhA~ zy{@|6HRmSqgpA`}vr1P>z1Amt%-+<2e0z;jFR~uQPj4}73}zB8~+WK Jrt5g>;}cyCnV 1 and args.stream: + raise ValueError("Can't stream multiple completions with openai CLI") + + kwargs = {} + if args.model is not None: + kwargs["model"] = args.model + resp = openai.Engine(id=args.id).generate( + completions=args.completions, + context=args.context, + length=args.length, + stream=args.stream, + temperature=args.temperature, + top_p=args.top_p, + logprobs=args.logprobs, + stop=args.stop, + **kwargs, + ) + if not args.stream: + resp = [resp] + + for part in resp: + completions = len(part["data"]) + for c_idx, c in enumerate(part["data"]): + if completions > 1: + sys.stdout.write("===== Completion {} =====\n".format(c_idx)) + sys.stdout.write("".join(c["text"])) + if completions > 1: + sys.stdout.write("\n") + sys.stdout.flush() + + @classmethod + def list(cls, args): + engines = openai.Engine.list() + display(engines) + + +class ChatCompletion: + @classmethod + def create(cls, args): + if args.n is not None and args.n > 1 and args.stream: + raise ValueError( + "Can't stream chat completions with n>1 with the current CLI" + ) + + messages = [ + {"role": role, "content": content} for role, content in args.message + ] + + resp = openai.ChatCompletion.create( + # Required + model=args.model, + messages=messages, + # Optional + n=args.n, + max_tokens=100, + temperature=args.temperature, + top_p=args.top_p, + stop=args.stop, + stream=args.stream, + ) + if not args.stream: + resp = [resp] + + for part in resp: + choices = part["choices"] + for c_idx, c in enumerate(sorted(choices, key=lambda s: s["index"])): + if len(choices) > 1: + sys.stdout.write("===== Chat Completion {} =====\n".format(c_idx)) + sys.stdout.write(c["message"]["content"]) + if len(choices) > 1: + sys.stdout.write("\n") + sys.stdout.flush() + + +class Completion: + @classmethod + def create(cls, args): + if args.n is not None and args.n > 1 and args.stream: + raise ValueError("Can't stream completions with n>1 with the current CLI") + + if args.engine and args.model: + warnings.warn( + "In most cases, you should not be specifying both engine and model." + ) + + resp = openai.Completion.create( + engine=args.engine, + model=args.model, + n=args.n, + max_tokens=args.max_tokens, + logprobs=args.logprobs, + prompt=args.prompt, + stream=args.stream, + temperature=args.temperature, + top_p=args.top_p, + stop=args.stop, + echo=True, + ) + if not args.stream: + resp = [resp] + + for part in resp: + choices = part["choices"] + for c_idx, c in enumerate(sorted(choices, key=lambda s: s["index"])): + if len(choices) > 1: + sys.stdout.write("===== Completion {} =====\n".format(c_idx)) + sys.stdout.write(c["text"]) + if len(choices) > 1: + sys.stdout.write("\n") + sys.stdout.flush() + + +class Deployment: + @classmethod + def get(cls, args): + resp = openai.Deployment.retrieve(id=args.id) + print(resp) + + @classmethod + def delete(cls, args): + model = openai.Deployment.delete(args.id) + print(model) + + @classmethod + def list(cls, args): + models = openai.Deployment.list() + print(models) + + @classmethod + def create(cls, args): + models = openai.Deployment.create(model=args.model, scale_settings={"scale_type": args.scale_type}) + print(models) + + +class Model: + @classmethod + def get(cls, args): + resp = openai.Model.retrieve(id=args.id) + print(resp) + + @classmethod + def delete(cls, args): + model = openai.Model.delete(args.id) + print(model) + + @classmethod + def list(cls, args): + models = openai.Model.list() + print(models) + + +class File: + @classmethod + def create(cls, args): + with open(args.file, "rb") as file_reader: + buffer_reader = BufferReader(file_reader.read(), desc="Upload progress") + resp = openai.File.create( + file=buffer_reader, + purpose=args.purpose, + user_provided_filename=args.file, + ) + print(resp) + + @classmethod + def get(cls, args): + resp = openai.File.retrieve(id=args.id) + print(resp) + + @classmethod + def delete(cls, args): + file = openai.File.delete(args.id) + print(file) + + @classmethod + def list(cls, args): + file = openai.File.list() + print(file) + + +class Image: + @classmethod + def create(cls, args): + resp = openai.Image.create( + prompt=args.prompt, + size=args.size, + n=args.num_images, + response_format=args.response_format, + ) + print(resp) + + @classmethod + def create_variation(cls, args): + with open(args.image, "rb") as file_reader: + buffer_reader = BufferReader(file_reader.read(), desc="Upload progress") + resp = openai.Image.create_variation( + image=buffer_reader, + size=args.size, + n=args.num_images, + response_format=args.response_format, + ) + print(resp) + + @classmethod + def create_edit(cls, args): + with open(args.image, "rb") as file_reader: + image_reader = BufferReader(file_reader.read(), desc="Upload progress") + mask_reader = None + if args.mask is not None: + with open(args.mask, "rb") as file_reader: + mask_reader = BufferReader(file_reader.read(), desc="Upload progress") + resp = openai.Image.create_edit( + image=image_reader, + mask=mask_reader, + prompt=args.prompt, + size=args.size, + n=args.num_images, + response_format=args.response_format, + ) + print(resp) + + +class Audio: + @classmethod + def transcribe(cls, args): + with open(args.file, "rb") as r: + file_reader = BufferReader(r.read(), desc="Upload progress") + + resp = openai.Audio.transcribe_raw( + # Required + model=args.model, + file=file_reader, + filename=args.file, + # Optional + response_format=args.response_format, + language=args.language, + temperature=args.temperature, + prompt=args.prompt, + ) + print(resp) + + @classmethod + def translate(cls, args): + with open(args.file, "rb") as r: + file_reader = BufferReader(r.read(), desc="Upload progress") + resp = openai.Audio.translate_raw( + # Required + model=args.model, + file=file_reader, + filename=args.file, + # Optional + response_format=args.response_format, + language=args.language, + temperature=args.temperature, + prompt=args.prompt, + ) + print(resp) + + +class FineTune: + @classmethod + def list(cls, args): + resp = openai.FineTune.list() + print(resp) + + @classmethod + def _is_url(cls, file: str): + return file.lower().startswith("http") + + @classmethod + def _download_file_from_public_url(cls, url: str) -> Optional[bytes]: + resp = requests.get(url) + if resp.status_code == 200: + return resp.content + else: + return None + + @classmethod + def _maybe_upload_file( + cls, + file: Optional[str] = None, + content: Optional[bytes] = None, + user_provided_file: Optional[str] = None, + check_if_file_exists: bool = True, + ): + # Exactly one of `file` or `content` must be provided + if (file is None) == (content is None): + raise ValueError("Exactly one of `file` or `content` must be provided") + + if content is None: + assert file is not None + with open(file, "rb") as f: + content = f.read() + + if check_if_file_exists: + bytes = len(content) + matching_files = openai.File.find_matching_files( + name=user_provided_file or f.name, bytes=bytes, purpose="fine-tune" + ) + if len(matching_files) > 0: + file_ids = [f["id"] for f in matching_files] + sys.stdout.write( + "Found potentially duplicated files with name '{name}', purpose 'fine-tune' and size {size} bytes\n".format( + name=os.path.basename(matching_files[0]["filename"]), + size=matching_files[0]["bytes"] + if "bytes" in matching_files[0] + else matching_files[0]["size"], + ) + ) + sys.stdout.write("\n".join(file_ids)) + while True: + sys.stdout.write( + "\nEnter file ID to reuse an already uploaded file, or an empty string to upload this file anyway: " + ) + inp = sys.stdin.readline().strip() + if inp in file_ids: + sys.stdout.write( + "Reusing already uploaded file: {id}\n".format(id=inp) + ) + return inp + elif inp == "": + break + else: + sys.stdout.write( + "File id '{id}' is not among the IDs of the potentially duplicated files\n".format( + id=inp + ) + ) + + buffer_reader = BufferReader(content, desc="Upload progress") + resp = openai.File.create( + file=buffer_reader, + purpose="fine-tune", + user_provided_filename=user_provided_file or file, + ) + sys.stdout.write( + "Uploaded file from {file}: {id}\n".format( + file=user_provided_file or file, id=resp["id"] + ) + ) + return resp["id"] + + @classmethod + def _get_or_upload(cls, file, check_if_file_exists=True): + try: + # 1. If it's a valid file, use it + openai.File.retrieve(file) + return file + except openai.error.InvalidRequestError: + pass + if os.path.isfile(file): + # 2. If it's a file on the filesystem, upload it + return cls._maybe_upload_file( + file=file, check_if_file_exists=check_if_file_exists + ) + if cls._is_url(file): + # 3. If it's a URL, download it temporarily + content = cls._download_file_from_public_url(file) + if content is not None: + return cls._maybe_upload_file( + content=content, + check_if_file_exists=check_if_file_exists, + user_provided_file=file, + ) + return file + + @classmethod + def create(cls, args): + create_args = { + "training_file": cls._get_or_upload( + args.training_file, args.check_if_files_exist + ), + } + if args.validation_file: + create_args["validation_file"] = cls._get_or_upload( + args.validation_file, args.check_if_files_exist + ) + + for hparam in ( + "model", + "suffix", + "n_epochs", + "batch_size", + "learning_rate_multiplier", + "prompt_loss_weight", + "compute_classification_metrics", + "classification_n_classes", + "classification_positive_class", + "classification_betas", + ): + attr = getattr(args, hparam) + if attr is not None: + create_args[hparam] = attr + + resp = openai.FineTune.create(**create_args) + + if args.no_follow: + print(resp) + return + + sys.stdout.write( + "Created fine-tune: {job_id}\n" + "Streaming events until fine-tuning is complete...\n\n" + "(Ctrl-C will interrupt the stream, but not cancel the fine-tune)\n".format( + job_id=resp["id"] + ) + ) + cls._stream_events(resp["id"]) + + @classmethod + def get(cls, args): + resp = openai.FineTune.retrieve(id=args.id) + print(resp) + + @classmethod + def results(cls, args): + fine_tune = openai.FineTune.retrieve(id=args.id) + if "result_files" not in fine_tune or len(fine_tune["result_files"]) == 0: + raise openai.error.InvalidRequestError( + f"No results file available for fine-tune {args.id}", "id" + ) + result_file = openai.FineTune.retrieve(id=args.id)["result_files"][0] + resp = openai.File.download(id=result_file["id"]) + print(resp.decode("utf-8")) + + @classmethod + def events(cls, args): + if args.stream: + raise openai.error.OpenAIError( + message=( + "The --stream parameter is deprecated, use fine_tunes.follow " + "instead:\n\n" + " openai api fine_tunes.follow -i {id}\n".format(id=args.id) + ), + ) + + resp = openai.FineTune.list_events(id=args.id) # type: ignore + print(resp) + + @classmethod + def follow(cls, args): + cls._stream_events(args.id) + + @classmethod + def _stream_events(cls, job_id): + def signal_handler(sig, frame): + status = openai.FineTune.retrieve(job_id).status + sys.stdout.write( + "\nStream interrupted. Job is still {status}.\n" + "To resume the stream, run:\n\n" + " openai api fine_tunes.follow -i {job_id}\n\n" + "To cancel your job, run:\n\n" + " openai api fine_tunes.cancel -i {job_id}\n\n".format( + status=status, job_id=job_id + ) + ) + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + + events = openai.FineTune.stream_events(job_id) + # TODO(rachel): Add a nifty spinner here. + try: + for event in events: + sys.stdout.write( + "[%s] %s" + % ( + datetime.datetime.fromtimestamp(event["created_at"]), + event["message"], + ) + ) + sys.stdout.write("\n") + sys.stdout.flush() + except Exception: + sys.stdout.write( + "\nStream interrupted (client disconnected).\n" + "To resume the stream, run:\n\n" + " openai api fine_tunes.follow -i {job_id}\n\n".format(job_id=job_id) + ) + return + + resp = openai.FineTune.retrieve(id=job_id) + status = resp["status"] + if status == "succeeded": + sys.stdout.write("\nJob complete! Status: succeeded 🎉") + sys.stdout.write( + "\nTry out your fine-tuned model:\n\n" + "openai api completions.create -m {model} -p ".format( + model=resp["fine_tuned_model"] + ) + ) + elif status == "failed": + sys.stdout.write( + "\nJob failed. Please contact support@openai.com if you need assistance." + ) + sys.stdout.write("\n") + + @classmethod + def cancel(cls, args): + resp = openai.FineTune.cancel(id=args.id) + print(resp) + + @classmethod + def delete(cls, args): + resp = openai.FineTune.delete(sid=args.id) + print(resp) + + @classmethod + def prepare_data(cls, args): + sys.stdout.write("Analyzing...\n") + fname = args.file + auto_accept = args.quiet + df, remediation = read_any_format(fname) + apply_necessary_remediation(None, remediation) + + validators = get_validators() + + apply_validators( + df, + fname, + remediation, + validators, + auto_accept, + write_out_file_func=write_out_file, + ) + + +class WandbLogger: + @classmethod + def sync(cls, args): + import openai.wandb_logger + + resp = openai.wandb_logger.WandbLogger.sync( + id=args.id, + n_fine_tunes=args.n_fine_tunes, + project=args.project, + entity=args.entity, + force=args.force, + ) + print(resp) + + +def tools_register(parser): + subparsers = parser.add_subparsers( + title="Tools", help="Convenience client side tools" + ) + + def help(args): + parser.print_help() + + parser.set_defaults(func=help) + + sub = subparsers.add_parser("fine_tunes.prepare_data") + sub.add_argument( + "-f", + "--file", + required=True, + help="JSONL, JSON, CSV, TSV, TXT or XLSX file containing prompt-completion examples to be analyzed." + "This should be the local file path.", + ) + sub.add_argument( + "-q", + "--quiet", + required=False, + action="store_true", + help="Auto accepts all suggestions, without asking for user input. To be used within scripts.", + ) + sub.set_defaults(func=FineTune.prepare_data) + + +def api_register(parser): + # Engine management + subparsers = parser.add_subparsers(help="All API subcommands") + + def help(args): + parser.print_help() + + parser.set_defaults(func=help) + + sub = subparsers.add_parser("engines.list") + sub.set_defaults(func=Engine.list) + + sub = subparsers.add_parser("engines.get") + sub.add_argument("-i", "--id", required=True) + sub.set_defaults(func=Engine.get) + + sub = subparsers.add_parser("engines.update") + sub.add_argument("-i", "--id", required=True) + sub.add_argument("-r", "--replicas", type=int) + sub.set_defaults(func=Engine.update) + + sub = subparsers.add_parser("engines.generate") + sub.add_argument("-i", "--id", required=True) + sub.add_argument( + "--stream", help="Stream tokens as they're ready.", action="store_true" + ) + sub.add_argument("-c", "--context", help="An optional context to generate from") + sub.add_argument("-l", "--length", help="How many tokens to generate", type=int) + sub.add_argument( + "-t", + "--temperature", + help="""What sampling temperature to use. Higher values means the model will take more risks. Try 0.9 for more creative applications, and 0 (argmax sampling) for ones with a well-defined answer. + +Mutually exclusive with `top_p`.""", + type=float, + ) + sub.add_argument( + "-p", + "--top_p", + help="""An alternative to sampling with temperature, called nucleus sampling, where the considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10%% probability mass are considered. + + Mutually exclusive with `temperature`.""", + type=float, + ) + sub.add_argument( + "-n", + "--completions", + help="How many parallel completions to run on this context", + type=int, + ) + sub.add_argument( + "--logprobs", + help="Include the log probabilites on the `logprobs` most likely tokens. So for example, if `logprobs` is 10, the API will return a list of the 10 most likely tokens. If `logprobs` is supplied, the API will always return the logprob of the generated token, so there may be up to `logprobs+1` elements in the response.", + type=int, + ) + sub.add_argument( + "--stop", help="A stop sequence at which to stop generating tokens." + ) + sub.add_argument( + "-m", + "--model", + required=False, + help="A model (most commonly a model ID) to generate from. Defaults to the engine's default model.", + ) + sub.set_defaults(func=Engine.generate) + + # Chat Completions + sub = subparsers.add_parser("chat_completions.create") + + sub._action_groups.pop() + req = sub.add_argument_group("required arguments") + opt = sub.add_argument_group("optional arguments") + + req.add_argument( + "-m", + "--model", + help="The model to use.", + required=True, + ) + req.add_argument( + "-g", + "--message", + action="append", + nargs=2, + metavar=("ROLE", "CONTENT"), + help="A message in `{role} {content}` format. Use this argument multiple times to add multiple messages.", + required=True, + ) + opt.add_argument( + "-n", + "--n", + help="How many completions to generate for the conversation.", + type=int, + ) + opt.add_argument( + "-M", "--max-tokens", help="The maximum number of tokens to generate.", type=int + ) + opt.add_argument( + "-t", + "--temperature", + help="""What sampling temperature to use. Higher values means the model will take more risks. Try 0.9 for more creative applications, and 0 (argmax sampling) for ones with a well-defined answer. + +Mutually exclusive with `top_p`.""", + type=float, + ) + opt.add_argument( + "-P", + "--top_p", + help="""An alternative to sampling with temperature, called nucleus sampling, where the considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10%% probability mass are considered. + + Mutually exclusive with `temperature`.""", + type=float, + ) + opt.add_argument( + "--stop", + help="A stop sequence at which to stop generating tokens for the message.", + ) + opt.add_argument( + "--stream", help="Stream messages as they're ready.", action="store_true" + ) + sub.set_defaults(func=ChatCompletion.create) + + # Completions + sub = subparsers.add_parser("completions.create") + sub.add_argument( + "-e", + "--engine", + help="The engine to use. See https://platform.openai.com/docs/engines for more about what engines are available.", + ) + sub.add_argument( + "-m", + "--model", + help="The model to use. At most one of `engine` or `model` should be specified.", + ) + sub.add_argument( + "--stream", help="Stream tokens as they're ready.", action="store_true" + ) + sub.add_argument("-p", "--prompt", help="An optional prompt to complete from") + sub.add_argument( + "-M", "--max-tokens", help="The maximum number of tokens to generate", type=int + ) + sub.add_argument( + "-t", + "--temperature", + help="""What sampling temperature to use. Higher values means the model will take more risks. Try 0.9 for more creative applications, and 0 (argmax sampling) for ones with a well-defined answer. + +Mutually exclusive with `top_p`.""", + type=float, + ) + sub.add_argument( + "-P", + "--top_p", + help="""An alternative to sampling with temperature, called nucleus sampling, where the considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10%% probability mass are considered. + + Mutually exclusive with `temperature`.""", + type=float, + ) + sub.add_argument( + "-n", + "--n", + help="How many sub-completions to generate for each prompt.", + type=int, + ) + sub.add_argument( + "--logprobs", + help="Include the log probabilites on the `logprobs` most likely tokens, as well the chosen tokens. So for example, if `logprobs` is 10, the API will return a list of the 10 most likely tokens. If `logprobs` is 0, only the chosen tokens will have logprobs returned.", + type=int, + ) + sub.add_argument( + "--stop", help="A stop sequence at which to stop generating tokens." + ) + sub.set_defaults(func=Completion.create) + + # Deployments + sub = subparsers.add_parser("deployments.list") + sub.set_defaults(func=Deployment.list) + + sub = subparsers.add_parser("deployments.get") + sub.add_argument("-i", "--id", required=True, help="The deployment ID") + sub.set_defaults(func=Deployment.get) + + sub = subparsers.add_parser("deployments.delete") + sub.add_argument("-i", "--id", required=True, help="The deployment ID") + sub.set_defaults(func=Deployment.delete) + + sub = subparsers.add_parser("deployments.create") + sub.add_argument("-m", "--model", required=True, help="The model ID") + sub.add_argument("-s", "--scale_type", required=True, help="The scale type. Either 'manual' or 'standard'") + sub.set_defaults(func=Deployment.create) + + # Models + sub = subparsers.add_parser("models.list") + sub.set_defaults(func=Model.list) + + sub = subparsers.add_parser("models.get") + sub.add_argument("-i", "--id", required=True, help="The model ID") + sub.set_defaults(func=Model.get) + + sub = subparsers.add_parser("models.delete") + sub.add_argument("-i", "--id", required=True, help="The model ID") + sub.set_defaults(func=Model.delete) + + # Files + sub = subparsers.add_parser("files.create") + + sub.add_argument( + "-f", + "--file", + required=True, + help="File to upload", + ) + sub.add_argument( + "-p", + "--purpose", + help="Why are you uploading this file? (see https://platform.openai.com/docs/api-reference/ for purposes)", + required=True, + ) + sub.set_defaults(func=File.create) + + sub = subparsers.add_parser("files.get") + sub.add_argument("-i", "--id", required=True, help="The files ID") + sub.set_defaults(func=File.get) + + sub = subparsers.add_parser("files.delete") + sub.add_argument("-i", "--id", required=True, help="The files ID") + sub.set_defaults(func=File.delete) + + sub = subparsers.add_parser("files.list") + sub.set_defaults(func=File.list) + + # Finetune + sub = subparsers.add_parser("fine_tunes.list") + sub.set_defaults(func=FineTune.list) + + sub = subparsers.add_parser("fine_tunes.create") + sub.add_argument( + "-t", + "--training_file", + required=True, + help="JSONL file containing prompt-completion examples for training. This can " + "be the ID of a file uploaded through the OpenAI API (e.g. file-abcde12345), " + 'a local file path, or a URL that starts with "http".', + ) + sub.add_argument( + "-v", + "--validation_file", + help="JSONL file containing prompt-completion examples for validation. This can " + "be the ID of a file uploaded through the OpenAI API (e.g. file-abcde12345), " + 'a local file path, or a URL that starts with "http".', + ) + sub.add_argument( + "--no_check_if_files_exist", + dest="check_if_files_exist", + action="store_false", + help="If this argument is set and training_file or validation_file are file paths, immediately upload them. If this argument is not set, check if they may be duplicates of already uploaded files before uploading, based on file name and file size.", + ) + sub.add_argument( + "-m", + "--model", + help="The model to start fine-tuning from", + ) + sub.add_argument( + "--suffix", + help="If set, this argument can be used to customize the generated fine-tuned model name." + "All punctuation and whitespace in `suffix` will be replaced with a " + "single dash, and the string will be lower cased. The max " + "length of `suffix` is 40 chars. " + "The generated name will match the form `{base_model}:ft-{org-title}:{suffix}-{timestamp}`. " + 'For example, `openai api fine_tunes.create -t test.jsonl -m ada --suffix "custom model name" ' + "could generate a model with the name " + "ada:ft-your-org:custom-model-name-2022-02-15-04-21-04", + ) + sub.add_argument( + "--no_follow", + action="store_true", + help="If set, returns immediately after creating the job. Otherwise, streams events and waits for the job to complete.", + ) + sub.add_argument( + "--n_epochs", + type=int, + help="The number of epochs to train the model for. An epoch refers to one " + "full cycle through the training dataset.", + ) + sub.add_argument( + "--batch_size", + type=int, + help="The batch size to use for training. The batch size is the number of " + "training examples used to train a single forward and backward pass.", + ) + sub.add_argument( + "--learning_rate_multiplier", + type=float, + help="The learning rate multiplier to use for training. The fine-tuning " + "learning rate is determined by the original learning rate used for " + "pretraining multiplied by this value.", + ) + sub.add_argument( + "--prompt_loss_weight", + type=float, + help="The weight to use for the prompt loss. The optimum value here depends " + "depends on your use case. This determines how much the model prioritizes " + "learning from prompt tokens vs learning from completion tokens.", + ) + sub.add_argument( + "--compute_classification_metrics", + action="store_true", + help="If set, we calculate classification-specific metrics such as accuracy " + "and F-1 score using the validation set at the end of every epoch.", + ) + sub.set_defaults(compute_classification_metrics=None) + sub.add_argument( + "--classification_n_classes", + type=int, + help="The number of classes in a classification task. This parameter is " + "required for multiclass classification.", + ) + sub.add_argument( + "--classification_positive_class", + help="The positive class in binary classification. This parameter is needed " + "to generate precision, recall and F-1 metrics when doing binary " + "classification.", + ) + sub.add_argument( + "--classification_betas", + type=float, + nargs="+", + help="If this is provided, we calculate F-beta scores at the specified beta " + "values. The F-beta score is a generalization of F-1 score. This is only " + "used for binary classification.", + ) + sub.set_defaults(func=FineTune.create) + + sub = subparsers.add_parser("fine_tunes.get") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + sub.set_defaults(func=FineTune.get) + + sub = subparsers.add_parser("fine_tunes.results") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + sub.set_defaults(func=FineTune.results) + + sub = subparsers.add_parser("fine_tunes.events") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + + # TODO(rachel): Remove this in 1.0 + sub.add_argument( + "-s", + "--stream", + action="store_true", + help="[DEPRECATED] If set, events will be streamed until the job is done. Otherwise, " + "displays the event history to date.", + ) + sub.set_defaults(func=FineTune.events) + + sub = subparsers.add_parser("fine_tunes.follow") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + sub.set_defaults(func=FineTune.follow) + + sub = subparsers.add_parser("fine_tunes.cancel") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + sub.set_defaults(func=FineTune.cancel) + + sub = subparsers.add_parser("fine_tunes.delete") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + sub.set_defaults(func=FineTune.delete) + + # Image + sub = subparsers.add_parser("image.create") + sub.add_argument("-p", "--prompt", type=str, required=True) + sub.add_argument("-n", "--num-images", type=int, default=1) + sub.add_argument( + "-s", "--size", type=str, default="1024x1024", help="Size of the output image" + ) + sub.add_argument("--response-format", type=str, default="url") + sub.set_defaults(func=Image.create) + + sub = subparsers.add_parser("image.create_edit") + sub.add_argument("-p", "--prompt", type=str, required=True) + sub.add_argument("-n", "--num-images", type=int, default=1) + sub.add_argument( + "-I", + "--image", + type=str, + required=True, + help="Image to modify. Should be a local path and a PNG encoded image.", + ) + sub.add_argument( + "-s", "--size", type=str, default="1024x1024", help="Size of the output image" + ) + sub.add_argument("--response-format", type=str, default="url") + sub.add_argument( + "-M", + "--mask", + type=str, + required=False, + help="Path to a mask image. It should be the same size as the image you're editing and a RGBA PNG image. The Alpha channel acts as the mask.", + ) + sub.set_defaults(func=Image.create_edit) + + sub = subparsers.add_parser("image.create_variation") + sub.add_argument("-n", "--num-images", type=int, default=1) + sub.add_argument( + "-I", + "--image", + type=str, + required=True, + help="Image to modify. Should be a local path and a PNG encoded image.", + ) + sub.add_argument( + "-s", "--size", type=str, default="1024x1024", help="Size of the output image" + ) + sub.add_argument("--response-format", type=str, default="url") + sub.set_defaults(func=Image.create_variation) + + # Audio + # transcriptions + sub = subparsers.add_parser("audio.transcribe") + # Required + sub.add_argument("-m", "--model", type=str, default="whisper-1") + sub.add_argument("-f", "--file", type=str, required=True) + # Optional + sub.add_argument("--response-format", type=str) + sub.add_argument("--language", type=str) + sub.add_argument("-t", "--temperature", type=float) + sub.add_argument("--prompt", type=str) + sub.set_defaults(func=Audio.transcribe) + # translations + sub = subparsers.add_parser("audio.translate") + # Required + sub.add_argument("-m", "--model", type=str, default="whisper-1") + sub.add_argument("-f", "--file", type=str, required=True) + # Optional + sub.add_argument("--response-format", type=str) + sub.add_argument("--language", type=str) + sub.add_argument("-t", "--temperature", type=float) + sub.add_argument("--prompt", type=str) + sub.set_defaults(func=Audio.translate) + + +def wandb_register(parser): + subparsers = parser.add_subparsers( + title="wandb", help="Logging with Weights & Biases" + ) + + def help(args): + parser.print_help() + + parser.set_defaults(func=help) + + sub = subparsers.add_parser("sync") + sub.add_argument("-i", "--id", help="The id of the fine-tune job (optional)") + sub.add_argument( + "-n", + "--n_fine_tunes", + type=int, + default=None, + help="Number of most recent fine-tunes to log when an id is not provided. By default, every fine-tune is synced.", + ) + sub.add_argument( + "--project", + default="GPT-3", + help="""Name of the project where you're sending runs. By default, it is "GPT-3".""", + ) + sub.add_argument( + "--entity", + help="Username or team name where you're sending runs. By default, your default entity is used, which is usually your username.", + ) + sub.add_argument( + "--force", + action="store_true", + help="Forces logging and overwrite existing wandb run of the same fine-tune.", + ) + sub.set_defaults(force=False) + sub.set_defaults(func=WandbLogger.sync) diff --git a/lib/openai/datalib.py b/lib/openai/datalib.py new file mode 100644 index 0000000..2781cfc --- /dev/null +++ b/lib/openai/datalib.py @@ -0,0 +1,56 @@ +""" +This module helps make data libraries like `numpy` and `pandas` optional dependencies. + +The libraries add up to 130MB+, which makes it challenging to deploy applications +using this library in environments with code size constraints, like AWS Lambda. + +This module serves as an import proxy and provides a few utilities for dealing with the optionality. + +Since the primary use case of this library (talking to the OpenAI API) doesn’t generally require data libraries, +it’s safe to make them optional. The rare case when data libraries are needed in the client is handled through +assertions with instructive error messages. + +See also `setup.py`. + +""" +try: + import numpy +except ImportError: + numpy = None + +try: + import pandas +except ImportError: + pandas = None + +HAS_NUMPY = bool(numpy) +HAS_PANDAS = bool(pandas) + +INSTRUCTIONS = """ + +OpenAI error: + + missing `{library}` + +This feature requires additional dependencies: + + $ pip install openai[datalib] + +""" + +NUMPY_INSTRUCTIONS = INSTRUCTIONS.format(library="numpy") +PANDAS_INSTRUCTIONS = INSTRUCTIONS.format(library="pandas") + + +class MissingDependencyError(Exception): + pass + + +def assert_has_numpy(): + if not HAS_NUMPY: + raise MissingDependencyError(NUMPY_INSTRUCTIONS) + + +def assert_has_pandas(): + if not HAS_PANDAS: + raise MissingDependencyError(PANDAS_INSTRUCTIONS) diff --git a/lib/openai/embeddings_utils.py b/lib/openai/embeddings_utils.py new file mode 100644 index 0000000..056c206 --- /dev/null +++ b/lib/openai/embeddings_utils.py @@ -0,0 +1,254 @@ +import textwrap as tr +from typing import List, Optional + +import matplotlib.pyplot as plt +import plotly.express as px +from scipy import spatial +from sklearn.decomposition import PCA +from sklearn.manifold import TSNE +from sklearn.metrics import average_precision_score, precision_recall_curve +from tenacity import retry, stop_after_attempt, wait_random_exponential + +import openai +from openai.datalib import numpy as np +from openai.datalib import pandas as pd + + +@retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6)) +def get_embedding(text: str, engine="text-similarity-davinci-001") -> List[float]: + + # replace newlines, which can negatively affect performance. + text = text.replace("\n", " ") + + return openai.Embedding.create(input=[text], engine=engine)["data"][0]["embedding"] + + +@retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6)) +async def aget_embedding( + text: str, engine="text-similarity-davinci-001" +) -> List[float]: + + # replace newlines, which can negatively affect performance. + text = text.replace("\n", " ") + + return (await openai.Embedding.acreate(input=[text], engine=engine))["data"][0][ + "embedding" + ] + + +@retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6)) +def get_embeddings( + list_of_text: List[str], engine="text-similarity-babbage-001" +) -> List[List[float]]: + assert len(list_of_text) <= 2048, "The batch size should not be larger than 2048." + + # replace newlines, which can negatively affect performance. + list_of_text = [text.replace("\n", " ") for text in list_of_text] + + data = openai.Embedding.create(input=list_of_text, engine=engine).data + data = sorted(data, key=lambda x: x["index"]) # maintain the same order as input. + return [d["embedding"] for d in data] + + +@retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6)) +async def aget_embeddings( + list_of_text: List[str], engine="text-similarity-babbage-001" +) -> List[List[float]]: + assert len(list_of_text) <= 2048, "The batch size should not be larger than 2048." + + # replace newlines, which can negatively affect performance. + list_of_text = [text.replace("\n", " ") for text in list_of_text] + + data = (await openai.Embedding.acreate(input=list_of_text, engine=engine)).data + data = sorted(data, key=lambda x: x["index"]) # maintain the same order as input. + return [d["embedding"] for d in data] + + +def cosine_similarity(a, b): + return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) + + +def plot_multiclass_precision_recall( + y_score, y_true_untransformed, class_list, classifier_name +): + """ + Precision-Recall plotting for a multiclass problem. It plots average precision-recall, per class precision recall and reference f1 contours. + + Code slightly modified, but heavily based on https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html + """ + n_classes = len(class_list) + y_true = pd.concat( + [(y_true_untransformed == class_list[i]) for i in range(n_classes)], axis=1 + ).values + + # For each class + precision = dict() + recall = dict() + average_precision = dict() + for i in range(n_classes): + precision[i], recall[i], _ = precision_recall_curve(y_true[:, i], y_score[:, i]) + average_precision[i] = average_precision_score(y_true[:, i], y_score[:, i]) + + # A "micro-average": quantifying score on all classes jointly + precision_micro, recall_micro, _ = precision_recall_curve( + y_true.ravel(), y_score.ravel() + ) + average_precision_micro = average_precision_score(y_true, y_score, average="micro") + print( + str(classifier_name) + + " - Average precision score over all classes: {0:0.2f}".format( + average_precision_micro + ) + ) + + # setup plot details + plt.figure(figsize=(9, 10)) + f_scores = np.linspace(0.2, 0.8, num=4) + lines = [] + labels = [] + for f_score in f_scores: + x = np.linspace(0.01, 1) + y = f_score * x / (2 * x - f_score) + (l,) = plt.plot(x[y >= 0], y[y >= 0], color="gray", alpha=0.2) + plt.annotate("f1={0:0.1f}".format(f_score), xy=(0.9, y[45] + 0.02)) + + lines.append(l) + labels.append("iso-f1 curves") + (l,) = plt.plot(recall_micro, precision_micro, color="gold", lw=2) + lines.append(l) + labels.append( + "average Precision-recall (auprc = {0:0.2f})" "".format(average_precision_micro) + ) + + for i in range(n_classes): + (l,) = plt.plot(recall[i], precision[i], lw=2) + lines.append(l) + labels.append( + "Precision-recall for class `{0}` (auprc = {1:0.2f})" + "".format(class_list[i], average_precision[i]) + ) + + fig = plt.gcf() + fig.subplots_adjust(bottom=0.25) + plt.xlim([0.0, 1.0]) + plt.ylim([0.0, 1.05]) + plt.xlabel("Recall") + plt.ylabel("Precision") + plt.title(f"{classifier_name}: Precision-Recall curve for each class") + plt.legend(lines, labels) + + +def distances_from_embeddings( + query_embedding: List[float], + embeddings: List[List[float]], + distance_metric="cosine", +) -> List[List]: + """Return the distances between a query embedding and a list of embeddings.""" + distance_metrics = { + "cosine": spatial.distance.cosine, + "L1": spatial.distance.cityblock, + "L2": spatial.distance.euclidean, + "Linf": spatial.distance.chebyshev, + } + distances = [ + distance_metrics[distance_metric](query_embedding, embedding) + for embedding in embeddings + ] + return distances + + +def indices_of_nearest_neighbors_from_distances(distances) -> np.ndarray: + """Return a list of indices of nearest neighbors from a list of distances.""" + return np.argsort(distances) + + +def pca_components_from_embeddings( + embeddings: List[List[float]], n_components=2 +) -> np.ndarray: + """Return the PCA components of a list of embeddings.""" + pca = PCA(n_components=n_components) + array_of_embeddings = np.array(embeddings) + return pca.fit_transform(array_of_embeddings) + + +def tsne_components_from_embeddings( + embeddings: List[List[float]], n_components=2, **kwargs +) -> np.ndarray: + """Returns t-SNE components of a list of embeddings.""" + # use better defaults if not specified + if "init" not in kwargs.keys(): + kwargs["init"] = "pca" + if "learning_rate" not in kwargs.keys(): + kwargs["learning_rate"] = "auto" + tsne = TSNE(n_components=n_components, **kwargs) + array_of_embeddings = np.array(embeddings) + return tsne.fit_transform(array_of_embeddings) + + +def chart_from_components( + components: np.ndarray, + labels: Optional[List[str]] = None, + strings: Optional[List[str]] = None, + x_title="Component 0", + y_title="Component 1", + mark_size=5, + **kwargs, +): + """Return an interactive 2D chart of embedding components.""" + empty_list = ["" for _ in components] + data = pd.DataFrame( + { + x_title: components[:, 0], + y_title: components[:, 1], + "label": labels if labels else empty_list, + "string": ["
".join(tr.wrap(string, width=30)) for string in strings] + if strings + else empty_list, + } + ) + chart = px.scatter( + data, + x=x_title, + y=y_title, + color="label" if labels else None, + symbol="label" if labels else None, + hover_data=["string"] if strings else None, + **kwargs, + ).update_traces(marker=dict(size=mark_size)) + return chart + + +def chart_from_components_3D( + components: np.ndarray, + labels: Optional[List[str]] = None, + strings: Optional[List[str]] = None, + x_title: str = "Component 0", + y_title: str = "Component 1", + z_title: str = "Compontent 2", + mark_size: int = 5, + **kwargs, +): + """Return an interactive 3D chart of embedding components.""" + empty_list = ["" for _ in components] + data = pd.DataFrame( + { + x_title: components[:, 0], + y_title: components[:, 1], + z_title: components[:, 2], + "label": labels if labels else empty_list, + "string": ["
".join(tr.wrap(string, width=30)) for string in strings] + if strings + else empty_list, + } + ) + chart = px.scatter_3d( + data, + x=x_title, + y=y_title, + z=z_title, + color="label" if labels else None, + symbol="label" if labels else None, + hover_data=["string"] if strings else None, + **kwargs, + ).update_traces(marker=dict(size=mark_size)) + return chart diff --git a/lib/openai/error.py b/lib/openai/error.py new file mode 100644 index 0000000..d22e71c --- /dev/null +++ b/lib/openai/error.py @@ -0,0 +1,168 @@ +import openai + + +class OpenAIError(Exception): + def __init__( + self, + message=None, + http_body=None, + http_status=None, + json_body=None, + headers=None, + code=None, + ): + super(OpenAIError, self).__init__(message) + + if http_body and hasattr(http_body, "decode"): + try: + http_body = http_body.decode("utf-8") + except BaseException: + http_body = ( + "" + ) + + self._message = message + self.http_body = http_body + self.http_status = http_status + self.json_body = json_body + self.headers = headers or {} + self.code = code + self.request_id = self.headers.get("request-id", None) + self.error = self.construct_error_object() + self.organization = self.headers.get("openai-organization", None) + + def __str__(self): + msg = self._message or "" + if self.request_id is not None: + return "Request {0}: {1}".format(self.request_id, msg) + else: + return msg + + # Returns the underlying `Exception` (base class) message, which is usually + # the raw message returned by OpenAI's API. This was previously available + # in python2 via `error.message`. Unlike `str(error)`, it omits "Request + # req_..." from the beginning of the string. + @property + def user_message(self): + return self._message + + def __repr__(self): + return "%s(message=%r, http_status=%r, request_id=%r)" % ( + self.__class__.__name__, + self._message, + self.http_status, + self.request_id, + ) + + def construct_error_object(self): + if ( + self.json_body is None + or "error" not in self.json_body + or not isinstance(self.json_body["error"], dict) + ): + return None + + return openai.api_resources.error_object.ErrorObject.construct_from( + self.json_body["error"] + ) + + +class APIError(OpenAIError): + pass + + +class TryAgain(OpenAIError): + pass + + +class Timeout(OpenAIError): + pass + + +class APIConnectionError(OpenAIError): + def __init__( + self, + message, + http_body=None, + http_status=None, + json_body=None, + headers=None, + code=None, + should_retry=False, + ): + super(APIConnectionError, self).__init__( + message, http_body, http_status, json_body, headers, code + ) + self.should_retry = should_retry + + +class InvalidRequestError(OpenAIError): + def __init__( + self, + message, + param, + code=None, + http_body=None, + http_status=None, + json_body=None, + headers=None, + ): + super(InvalidRequestError, self).__init__( + message, http_body, http_status, json_body, headers, code + ) + self.param = param + + def __repr__(self): + return "%s(message=%r, param=%r, code=%r, http_status=%r, " "request_id=%r)" % ( + self.__class__.__name__, + self._message, + self.param, + self.code, + self.http_status, + self.request_id, + ) + + def __reduce__(self): + return type(self), ( + self._message, + self.param, + self.code, + self.http_body, + self.http_status, + self.json_body, + self.headers, + ) + + +class AuthenticationError(OpenAIError): + pass + + +class PermissionError(OpenAIError): + pass + + +class RateLimitError(OpenAIError): + pass + + +class ServiceUnavailableError(OpenAIError): + pass + + +class InvalidAPIType(OpenAIError): + pass + + +class SignatureVerificationError(OpenAIError): + def __init__(self, message, sig_header, http_body=None): + super(SignatureVerificationError, self).__init__(message, http_body) + self.sig_header = sig_header + + def __reduce__(self): + return type(self), ( + self._message, + self.sig_header, + self.http_body, + ) diff --git a/lib/openai/object_classes.py b/lib/openai/object_classes.py new file mode 100644 index 0000000..5f72bd7 --- /dev/null +++ b/lib/openai/object_classes.py @@ -0,0 +1,11 @@ +from openai import api_resources +from openai.api_resources.experimental.completion_config import CompletionConfig + +OBJECT_CLASSES = { + "engine": api_resources.Engine, + "experimental.completion_config": CompletionConfig, + "file": api_resources.File, + "fine-tune": api_resources.FineTune, + "model": api_resources.Model, + "deployment": api_resources.Deployment, +} diff --git a/lib/openai/openai_object.py b/lib/openai/openai_object.py new file mode 100644 index 0000000..c0af6bb --- /dev/null +++ b/lib/openai/openai_object.py @@ -0,0 +1,347 @@ +import json +from copy import deepcopy +from typing import Optional, Tuple, Union + +import openai +from openai import api_requestor, util +from openai.openai_response import OpenAIResponse +from openai.util import ApiType + + +class OpenAIObject(dict): + api_base_override = None + + def __init__( + self, + id=None, + api_key=None, + api_version=None, + api_type=None, + organization=None, + response_ms: Optional[int] = None, + api_base=None, + engine=None, + **params, + ): + super(OpenAIObject, self).__init__() + + if response_ms is not None and not isinstance(response_ms, int): + raise TypeError(f"response_ms is a {type(response_ms).__name__}.") + self._response_ms = response_ms + + self._retrieve_params = params + + object.__setattr__(self, "api_key", api_key) + object.__setattr__(self, "api_version", api_version) + object.__setattr__(self, "api_type", api_type) + object.__setattr__(self, "organization", organization) + object.__setattr__(self, "api_base_override", api_base) + object.__setattr__(self, "engine", engine) + + if id: + self["id"] = id + + @property + def response_ms(self) -> Optional[int]: + return self._response_ms + + def __setattr__(self, k, v): + if k[0] == "_" or k in self.__dict__: + return super(OpenAIObject, self).__setattr__(k, v) + + self[k] = v + return None + + def __getattr__(self, k): + if k[0] == "_": + raise AttributeError(k) + try: + return self[k] + except KeyError as err: + raise AttributeError(*err.args) + + def __delattr__(self, k): + if k[0] == "_" or k in self.__dict__: + return super(OpenAIObject, self).__delattr__(k) + else: + del self[k] + + def __setitem__(self, k, v): + if v == "": + raise ValueError( + "You cannot set %s to an empty string. " + "We interpret empty strings as None in requests." + "You may set %s.%s = None to delete the property" % (k, str(self), k) + ) + super(OpenAIObject, self).__setitem__(k, v) + + def __delitem__(self, k): + raise NotImplementedError("del is not supported") + + # Custom unpickling method that uses `update` to update the dictionary + # without calling __setitem__, which would fail if any value is an empty + # string + def __setstate__(self, state): + self.update(state) + + # Custom pickling method to ensure the instance is pickled as a custom + # class and not as a dict, otherwise __setstate__ would not be called when + # unpickling. + def __reduce__(self): + reduce_value = ( + type(self), # callable + ( # args + self.get("id", None), + self.api_key, + self.api_version, + self.api_type, + self.organization, + ), + dict(self), # state + ) + return reduce_value + + @classmethod + def construct_from( + cls, + values, + api_key: Optional[str] = None, + api_version=None, + organization=None, + engine=None, + response_ms: Optional[int] = None, + ): + instance = cls( + values.get("id"), + api_key=api_key, + api_version=api_version, + organization=organization, + engine=engine, + response_ms=response_ms, + ) + instance.refresh_from( + values, + api_key=api_key, + api_version=api_version, + organization=organization, + response_ms=response_ms, + ) + return instance + + def refresh_from( + self, + values, + api_key=None, + api_version=None, + api_type=None, + organization=None, + response_ms: Optional[int] = None, + ): + self.api_key = api_key or getattr(values, "api_key", None) + self.api_version = api_version or getattr(values, "api_version", None) + self.api_type = api_type or getattr(values, "api_type", None) + self.organization = organization or getattr(values, "organization", None) + self._response_ms = response_ms or getattr(values, "_response_ms", None) + + # Wipe old state before setting new. + self.clear() + for k, v in values.items(): + super(OpenAIObject, self).__setitem__( + k, util.convert_to_openai_object(v, api_key, api_version, organization) + ) + + self._previous = values + + @classmethod + def api_base(cls): + return None + + def request( + self, + method, + url, + params=None, + headers=None, + stream=False, + plain_old_data=False, + request_id: Optional[str] = None, + request_timeout: Optional[Union[float, Tuple[float, float]]] = None, + ): + if params is None: + params = self._retrieve_params + requestor = api_requestor.APIRequestor( + key=self.api_key, + api_base=self.api_base_override or self.api_base(), + api_type=self.api_type, + api_version=self.api_version, + organization=self.organization, + ) + response, stream, api_key = requestor.request( + method, + url, + params=params, + stream=stream, + headers=headers, + request_id=request_id, + request_timeout=request_timeout, + ) + + if stream: + assert not isinstance(response, OpenAIResponse) # must be an iterator + return ( + util.convert_to_openai_object( + line, + api_key, + self.api_version, + self.organization, + plain_old_data=plain_old_data, + ) + for line in response + ) + else: + return util.convert_to_openai_object( + response, + api_key, + self.api_version, + self.organization, + plain_old_data=plain_old_data, + ) + + async def arequest( + self, + method, + url, + params=None, + headers=None, + stream=False, + plain_old_data=False, + request_id: Optional[str] = None, + request_timeout: Optional[Union[float, Tuple[float, float]]] = None, + ): + if params is None: + params = self._retrieve_params + requestor = api_requestor.APIRequestor( + key=self.api_key, + api_base=self.api_base_override or self.api_base(), + api_type=self.api_type, + api_version=self.api_version, + organization=self.organization, + ) + response, stream, api_key = await requestor.arequest( + method, + url, + params=params, + stream=stream, + headers=headers, + request_id=request_id, + request_timeout=request_timeout, + ) + + if stream: + assert not isinstance(response, OpenAIResponse) # must be an iterator + return ( + util.convert_to_openai_object( + line, + api_key, + self.api_version, + self.organization, + plain_old_data=plain_old_data, + ) + for line in response + ) + else: + return util.convert_to_openai_object( + response, + api_key, + self.api_version, + self.organization, + plain_old_data=plain_old_data, + ) + + def __repr__(self): + ident_parts = [type(self).__name__] + + obj = self.get("object") + if isinstance(obj, str): + ident_parts.append(obj) + + if isinstance(self.get("id"), str): + ident_parts.append("id=%s" % (self.get("id"),)) + + unicode_repr = "<%s at %s> JSON: %s" % ( + " ".join(ident_parts), + hex(id(self)), + str(self), + ) + + return unicode_repr + + def __str__(self): + obj = self.to_dict_recursive() + return json.dumps(obj, sort_keys=True, indent=2) + + def to_dict(self): + return dict(self) + + def to_dict_recursive(self): + d = dict(self) + for k, v in d.items(): + if isinstance(v, OpenAIObject): + d[k] = v.to_dict_recursive() + elif isinstance(v, list): + d[k] = [ + e.to_dict_recursive() if isinstance(e, OpenAIObject) else e + for e in v + ] + return d + + @property + def openai_id(self): + return self.id + + @property + def typed_api_type(self): + return ( + ApiType.from_str(self.api_type) + if self.api_type + else ApiType.from_str(openai.api_type) + ) + + # This class overrides __setitem__ to throw exceptions on inputs that it + # doesn't like. This can cause problems when we try to copy an object + # wholesale because some data that's returned from the API may not be valid + # if it was set to be set manually. Here we override the class' copy + # arguments so that we can bypass these possible exceptions on __setitem__. + def __copy__(self): + copied = OpenAIObject( + self.get("id"), + self.api_key, + api_version=self.api_version, + api_type=self.api_type, + organization=self.organization, + ) + + copied._retrieve_params = self._retrieve_params + + for k, v in self.items(): + # Call parent's __setitem__ to avoid checks that we've added in the + # overridden version that can throw exceptions. + super(OpenAIObject, copied).__setitem__(k, v) + + return copied + + # This class overrides __setitem__ to throw exceptions on inputs that it + # doesn't like. This can cause problems when we try to copy an object + # wholesale because some data that's returned from the API may not be valid + # if it was set to be set manually. Here we override the class' copy + # arguments so that we can bypass these possible exceptions on __setitem__. + def __deepcopy__(self, memo): + copied = self.__copy__() + memo[id(self)] = copied + + for k, v in self.items(): + # Call parent's __setitem__ to avoid checks that we've added in the + # overridden version that can throw exceptions. + super(OpenAIObject, copied).__setitem__(k, deepcopy(v, memo)) + + return copied diff --git a/lib/openai/openai_response.py b/lib/openai/openai_response.py new file mode 100644 index 0000000..9954247 --- /dev/null +++ b/lib/openai/openai_response.py @@ -0,0 +1,20 @@ +from typing import Optional + + +class OpenAIResponse: + def __init__(self, data, headers): + self._headers = headers + self.data = data + + @property + def request_id(self) -> Optional[str]: + return self._headers.get("request-id") + + @property + def organization(self) -> Optional[str]: + return self._headers.get("OpenAI-Organization") + + @property + def response_ms(self) -> Optional[int]: + h = self._headers.get("Openai-Processing-Ms") + return None if h is None else round(float(h)) diff --git a/lib/openai/py.typed b/lib/openai/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lib/openai/tests/__init__.py b/lib/openai/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/openai/tests/__pycache__/__init__.cpython-39.pyc b/lib/openai/tests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c0380faa40a081883a58c2ab3e787a056349da8 GIT binary patch literal 173 zcmYe~<>g`k0?)OQDIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o11#*(xTqIJKxa zCOEk$vmnMLwK%&ZzaS>Ppd`LHBQYhlD5fN}xWp*NCo?IgII|>Gw;(Y&J25@AI3~X! oH7_w!A0%C@A0MBYmst`YuUAlci^C>2KczG$)edCWXCP((0Qo5_&;S4c literal 0 HcmV?d00001 diff --git a/lib/openai/tests/__pycache__/test_api_requestor.cpython-39.pyc b/lib/openai/tests/__pycache__/test_api_requestor.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..317f652cabb3e9291fdc9ed32632ea66f08252b9 GIT binary patch literal 2149 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 literal 0 HcmV?d00001 diff --git a/lib/openai/tests/asyncio/__pycache__/test_endpoints.cpython-39.pyc b/lib/openai/tests/asyncio/__pycache__/test_endpoints.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81db62448321dd794aed12b03c3fd7b1c261b73f GIT binary patch literal 6667 zcmeI0O>Y}T7{_P!-LCDVAq_2sl2Q=3v?(p6ND-8mqSB%&98y%VvShZNiM`HxcRMp~ zX>cyI;uerNA`Zzh2lx{G7JI8W!e~VNyre$W_l9pkX+&nv@-3S8*}e^JW@Psq-=TyX zUAODCsb69ivmX+_%pB%Id5C$e1Z9Pl*&!&atiq~L)>w^AK{>^y*deMNqx%o&ioM1GESJ%b1RCFX9>UE|BlgYheR zJb{&#mey*DOEtd|Mtq(HD#$Dqu%K>aPAZaKs-|MGQ5-`r{hcYKgc^rh9bybfJT7m}(M<dS4KIfBc6q_&Beg@I4LyRz8$Msgt=%PkS6jW4)d zRY}@tr>ZGCun5sWffpJ#!rKiQDt5dS$2X5Fvo_38 ze3S_hvmQXaAx5XiF|wDQOx^5+a{gW*=Yx4%%?{3&nGmRLQ|LO4eTT0 zE91C{iXI;EICEQ_By4dhW}r#$V4fYp5xLSyyya`qDls0Y)OlJl#XQuXukHf$PSmx* zx7xA$6*{1BVXV+y5--D(YeyzB<3x41&|<|==o7~>v#%b+mSAX!gJ@ zAvn018(O{zkB&u^QPdmI@_bR3p?4w*CA}zQ5JEh(<`$KUV}W)qaH6E1hfN%Zxih<$ zFdo%i9XEk{t^+1cVC{9xPT{aJ908%^y-;-yR6e&~InMhoJS!c@2t7(?sCWx%&xpmY z@%QHj+$ge1mC*$Wznq~&SzWt3{0d<-nX_T%j-})le2FZ$9+;HG)8~o2{2rnD`WAAA zUl@1kv&|`(6I@%QVh;)r6dou%PM1TW(4yg_rP37t7;?Z=;4J4?810?5i&|1r!WaSmb( z*Z!|K19#7?8+Qv4>LW<dY&xk*l8Gwv4RTHw6B9c0^ePave%vG>bC2(Vbw@XVBec*h%>IYA1jm}%@) zH#2uM%uW$|Wfm5D&64BV-Lx-%G2q%blpWarc9h%>qULb2tFVyVLUfu|Obcw}Jw0mH zsJH|@ElA@ZuGxj8LxETfwtJ6_p0nL_Kp;eb>tVZ3z;;^$GTiQKBO)ly!h~?P_IL{U zX|CT87qR{UW)r7rO1q#<_9D$u(wazKhAAWhU}Ef&`yt%dXG9eW0eFsxcOdg!h)zeT zbP}-q{93(0tY+?zk^ANRjANt5-I@w(imK_NK91VkdRGO$YnKQuM=S9@0`l39_+_+- a#AE7F&+vewV=7zTl;^_bconbYRsREYU?a!? literal 0 HcmV?d00001 diff --git a/lib/openai/tests/asyncio/test_endpoints.py b/lib/openai/tests/asyncio/test_endpoints.py new file mode 100644 index 0000000..1b146e6 --- /dev/null +++ b/lib/openai/tests/asyncio/test_endpoints.py @@ -0,0 +1,90 @@ +import io +import json + +import pytest +from aiohttp import ClientSession + +import openai +from openai import error + +pytestmark = [pytest.mark.asyncio] + + +# FILE TESTS +async def test_file_upload(): + result = await openai.File.acreate( + file=io.StringIO( + json.dumps({"prompt": "test file data", "completion": "tada"}) + ), + purpose="fine-tune", + ) + assert result.purpose == "fine-tune" + assert "id" in result + + result = await openai.File.aretrieve(id=result.id) + assert result.status == "uploaded" + + +# COMPLETION TESTS +async def test_completions(): + result = await openai.Completion.acreate( + prompt="This was a test", n=5, engine="ada" + ) + assert len(result.choices) == 5 + + +async def test_completions_multiple_prompts(): + result = await openai.Completion.acreate( + prompt=["This was a test", "This was another test"], n=5, engine="ada" + ) + assert len(result.choices) == 10 + + +async def test_completions_model(): + result = await openai.Completion.acreate(prompt="This was a test", n=5, model="ada") + assert len(result.choices) == 5 + assert result.model.startswith("ada") + + +async def test_timeout_raises_error(): + # A query that should take awhile to return + with pytest.raises(error.Timeout): + await openai.Completion.acreate( + prompt="test" * 1000, + n=10, + model="ada", + max_tokens=100, + request_timeout=0.01, + ) + + +async def test_timeout_does_not_error(): + # A query that should be fast + await openai.Completion.acreate( + prompt="test", + model="ada", + request_timeout=10, + ) + + +async def test_completions_stream_finishes_global_session(): + async with ClientSession() as session: + openai.aiosession.set(session) + + # A query that should be fast + parts = [] + async for part in await openai.Completion.acreate( + prompt="test", model="ada", request_timeout=3, stream=True + ): + parts.append(part) + assert len(parts) > 1 + + +async def test_completions_stream_finishes_local_session(): + # A query that should be fast + parts = [] + async for part in await openai.Completion.acreate( + prompt="test", model="ada", request_timeout=3, stream=True + ): + parts.append(part) + assert len(parts) > 1 diff --git a/lib/openai/tests/test_api_requestor.py b/lib/openai/tests/test_api_requestor.py new file mode 100644 index 0000000..4998a0f --- /dev/null +++ b/lib/openai/tests/test_api_requestor.py @@ -0,0 +1,69 @@ +import json + +import pytest +import requests +from pytest_mock import MockerFixture + +from openai import Model +from openai.api_requestor import APIRequestor + + +@pytest.mark.requestor +def test_requestor_sets_request_id(mocker: MockerFixture) -> None: + # Fake out 'requests' and confirm that the X-Request-Id header is set. + + got_headers = {} + + def fake_request(self, *args, **kwargs): + nonlocal got_headers + got_headers = kwargs["headers"] + r = requests.Response() + r.status_code = 200 + r.headers["content-type"] = "application/json" + r._content = json.dumps({}).encode("utf-8") + return r + + mocker.patch("requests.sessions.Session.request", fake_request) + fake_request_id = "1234" + Model.retrieve("xxx", request_id=fake_request_id) # arbitrary API resource + got_request_id = got_headers.get("X-Request-Id") + assert got_request_id == fake_request_id + + +@pytest.mark.requestor +def test_requestor_open_ai_headers() -> None: + api_requestor = APIRequestor(key="test_key", api_type="open_ai") + headers = {"Test_Header": "Unit_Test_Header"} + headers = api_requestor.request_headers( + method="get", extra=headers, request_id="test_id" + ) + assert "Test_Header" in headers + assert headers["Test_Header"] == "Unit_Test_Header" + assert "Authorization" in headers + assert headers["Authorization"] == "Bearer test_key" + + +@pytest.mark.requestor +def test_requestor_azure_headers() -> None: + api_requestor = APIRequestor(key="test_key", api_type="azure") + headers = {"Test_Header": "Unit_Test_Header"} + headers = api_requestor.request_headers( + method="get", extra=headers, request_id="test_id" + ) + assert "Test_Header" in headers + assert headers["Test_Header"] == "Unit_Test_Header" + assert "api-key" in headers + assert headers["api-key"] == "test_key" + + +@pytest.mark.requestor +def test_requestor_azure_ad_headers() -> None: + api_requestor = APIRequestor(key="test_key", api_type="azure_ad") + headers = {"Test_Header": "Unit_Test_Header"} + headers = api_requestor.request_headers( + method="get", extra=headers, request_id="test_id" + ) + assert "Test_Header" in headers + assert headers["Test_Header"] == "Unit_Test_Header" + assert "Authorization" in headers + assert headers["Authorization"] == "Bearer test_key" diff --git a/lib/openai/tests/test_endpoints.py b/lib/openai/tests/test_endpoints.py new file mode 100644 index 0000000..c3fc109 --- /dev/null +++ b/lib/openai/tests/test_endpoints.py @@ -0,0 +1,88 @@ +import io +import json + +import pytest + +import openai +from openai import error + + +# FILE TESTS +def test_file_upload(): + result = openai.File.create( + file=io.StringIO( + json.dumps({"prompt": "test file data", "completion": "tada"}) + ), + purpose="fine-tune", + ) + assert result.purpose == "fine-tune" + assert "id" in result + + result = openai.File.retrieve(id=result.id) + assert result.status == "uploaded" + + +# CHAT COMPLETION TESTS +def test_chat_completions(): + result = openai.ChatCompletion.create( + model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello!"}] + ) + assert len(result.choices) == 1 + + +def test_chat_completions_multiple(): + result = openai.ChatCompletion.create( + model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hello!"}], n=5 + ) + assert len(result.choices) == 5 + + +def test_chat_completions_streaming(): + result = None + events = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello!"}], + stream=True, + ) + for result in events: + assert len(result.choices) == 1 + + +# COMPLETION TESTS +def test_completions(): + result = openai.Completion.create(prompt="This was a test", n=5, engine="ada") + assert len(result.choices) == 5 + + +def test_completions_multiple_prompts(): + result = openai.Completion.create( + prompt=["This was a test", "This was another test"], n=5, engine="ada" + ) + assert len(result.choices) == 10 + + +def test_completions_model(): + result = openai.Completion.create(prompt="This was a test", n=5, model="ada") + assert len(result.choices) == 5 + assert result.model.startswith("ada") + + +def test_timeout_raises_error(): + # A query that should take awhile to return + with pytest.raises(error.Timeout): + openai.Completion.create( + prompt="test" * 1000, + n=10, + model="ada", + max_tokens=100, + request_timeout=0.01, + ) + + +def test_timeout_does_not_error(): + # A query that should be fast + openai.Completion.create( + prompt="test", + model="ada", + request_timeout=10, + ) diff --git a/lib/openai/tests/test_exceptions.py b/lib/openai/tests/test_exceptions.py new file mode 100644 index 0000000..7760cdc --- /dev/null +++ b/lib/openai/tests/test_exceptions.py @@ -0,0 +1,40 @@ +import pickle + +import pytest + +import openai + +EXCEPTION_TEST_CASES = [ + openai.InvalidRequestError( + "message", + "param", + code=400, + http_body={"test": "test1"}, + http_status="fail", + json_body={"text": "iono some text"}, + headers={"request-id": "asasd"}, + ), + openai.error.AuthenticationError(), + openai.error.PermissionError(), + openai.error.RateLimitError(), + openai.error.ServiceUnavailableError(), + openai.error.SignatureVerificationError("message", "sig_header?"), + openai.error.APIConnectionError("message!", should_retry=True), + openai.error.TryAgain(), + openai.error.Timeout(), + openai.error.APIError( + message="message", + code=400, + http_body={"test": "test1"}, + http_status="fail", + json_body={"text": "iono some text"}, + headers={"request-id": "asasd"}, + ), + openai.error.OpenAIError(), +] + + +class TestExceptions: + @pytest.mark.parametrize("error", EXCEPTION_TEST_CASES) + def test_exceptions_are_pickleable(self, error) -> None: + assert error.__repr__() == pickle.loads(pickle.dumps(error)).__repr__() diff --git a/lib/openai/tests/test_file_cli.py b/lib/openai/tests/test_file_cli.py new file mode 100644 index 0000000..69ea29e --- /dev/null +++ b/lib/openai/tests/test_file_cli.py @@ -0,0 +1,39 @@ +import json +import subprocess +import time +from tempfile import NamedTemporaryFile + +STILL_PROCESSING = "File is still processing. Check back later." + + +def test_file_cli() -> None: + contents = json.dumps({"prompt": "1 + 3 =", "completion": "4"}) + "\n" + with NamedTemporaryFile(suffix=".jsonl", mode="wb") as train_file: + train_file.write(contents.encode("utf-8")) + train_file.flush() + create_output = subprocess.check_output( + ["openai", "api", "files.create", "-f", train_file.name, "-p", "fine-tune"] + ) + file_obj = json.loads(create_output) + assert file_obj["bytes"] == len(contents) + file_id: str = file_obj["id"] + assert file_id.startswith("file-") + start_time = time.time() + while True: + delete_result = subprocess.run( + ["openai", "api", "files.delete", "-i", file_id], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + if delete_result.returncode == 0: + break + elif STILL_PROCESSING in delete_result.stderr: + time.sleep(0.5) + if start_time + 60 < time.time(): + raise RuntimeError("timed out waiting for file to become available") + continue + else: + raise RuntimeError( + f"delete failed: stdout={delete_result.stdout} stderr={delete_result.stderr}" + ) diff --git a/lib/openai/tests/test_long_examples_validator.py b/lib/openai/tests/test_long_examples_validator.py new file mode 100644 index 0000000..0cac136 --- /dev/null +++ b/lib/openai/tests/test_long_examples_validator.py @@ -0,0 +1,58 @@ +import json +import subprocess +from tempfile import NamedTemporaryFile + +import pytest + +from openai.datalib import ( + HAS_NUMPY, + HAS_PANDAS, + NUMPY_INSTRUCTIONS, + PANDAS_INSTRUCTIONS, +) + + +@pytest.mark.skipif(not HAS_PANDAS, reason=PANDAS_INSTRUCTIONS) +@pytest.mark.skipif(not HAS_NUMPY, reason=NUMPY_INSTRUCTIONS) +def test_long_examples_validator() -> None: + """ + Ensures that long_examples_validator() handles previously applied recommendations, + namely dropped duplicates, without resulting in a KeyError. + """ + + # data + short_prompt = "a prompt " + long_prompt = short_prompt * 500 + + short_completion = "a completion " + long_completion = short_completion * 500 + + # the order of these matters + unprepared_training_data = [ + {"prompt": long_prompt, "completion": long_completion}, # 1 of 2 duplicates + {"prompt": short_prompt, "completion": short_completion}, + {"prompt": long_prompt, "completion": long_completion}, # 2 of 2 duplicates + ] + + with NamedTemporaryFile(suffix=".jsonl", mode="w") as training_data: + print(training_data.name) + for prompt_completion_row in unprepared_training_data: + training_data.write(json.dumps(prompt_completion_row) + "\n") + training_data.flush() + + prepared_data_cmd_output = subprocess.run( + [f"openai tools fine_tunes.prepare_data -f {training_data.name}"], + stdout=subprocess.PIPE, + text=True, + input="y\ny\ny\ny\ny", # apply all recommendations, one at a time + stderr=subprocess.PIPE, + encoding="utf-8", + shell=True, + ) + + # validate data was prepared successfully + assert prepared_data_cmd_output.stderr == "" + # validate get_long_indexes() applied during optional_fn() call in long_examples_validator() + assert "indices of the long examples has changed" in prepared_data_cmd_output.stdout + + return prepared_data_cmd_output.stdout diff --git a/lib/openai/tests/test_url_composition.py b/lib/openai/tests/test_url_composition.py new file mode 100644 index 0000000..5034354 --- /dev/null +++ b/lib/openai/tests/test_url_composition.py @@ -0,0 +1,209 @@ +from sys import api_version + +import pytest + +from openai import Completion, Engine +from openai.util import ApiType + + +@pytest.mark.url +def test_completions_url_composition_azure() -> None: + url = Completion.class_url("test_engine", "azure", "2021-11-01-preview") + assert ( + url + == "/openai/deployments/test_engine/completions?api-version=2021-11-01-preview" + ) + + +@pytest.mark.url +def test_completions_url_composition_azure_ad() -> None: + url = Completion.class_url("test_engine", "azure_ad", "2021-11-01-preview") + assert ( + url + == "/openai/deployments/test_engine/completions?api-version=2021-11-01-preview" + ) + + +@pytest.mark.url +def test_completions_url_composition_default() -> None: + url = Completion.class_url("test_engine") + assert url == "/engines/test_engine/completions" + + +@pytest.mark.url +def test_completions_url_composition_open_ai() -> None: + url = Completion.class_url("test_engine", "open_ai") + assert url == "/engines/test_engine/completions" + + +@pytest.mark.url +def test_completions_url_composition_invalid_type() -> None: + with pytest.raises(Exception): + url = Completion.class_url("test_engine", "invalid") + + +@pytest.mark.url +def test_completions_url_composition_instance_url_azure() -> None: + completion = Completion( + id="test_id", + engine="test_engine", + api_type="azure", + api_version="2021-11-01-preview", + ) + url = completion.instance_url() + assert ( + url + == "/openai/deployments/test_engine/completions/test_id?api-version=2021-11-01-preview" + ) + + +@pytest.mark.url +def test_completions_url_composition_instance_url_azure_ad() -> None: + completion = Completion( + id="test_id", + engine="test_engine", + api_type="azure_ad", + api_version="2021-11-01-preview", + ) + url = completion.instance_url() + assert ( + url + == "/openai/deployments/test_engine/completions/test_id?api-version=2021-11-01-preview" + ) + + +@pytest.mark.url +def test_completions_url_composition_instance_url_azure_no_version() -> None: + completion = Completion( + id="test_id", engine="test_engine", api_type="azure", api_version=None + ) + with pytest.raises(Exception): + completion.instance_url() + + +@pytest.mark.url +def test_completions_url_composition_instance_url_default() -> None: + completion = Completion(id="test_id", engine="test_engine") + url = completion.instance_url() + assert url == "/engines/test_engine/completions/test_id" + + +@pytest.mark.url +def test_completions_url_composition_instance_url_open_ai() -> None: + completion = Completion( + id="test_id", + engine="test_engine", + api_type="open_ai", + api_version="2021-11-01-preview", + ) + url = completion.instance_url() + assert url == "/engines/test_engine/completions/test_id" + + +@pytest.mark.url +def test_completions_url_composition_instance_url_invalid() -> None: + completion = Completion(id="test_id", engine="test_engine", api_type="invalid") + with pytest.raises(Exception): + url = completion.instance_url() + + +@pytest.mark.url +def test_completions_url_composition_instance_url_timeout_azure() -> None: + completion = Completion( + id="test_id", + engine="test_engine", + api_type="azure", + api_version="2021-11-01-preview", + ) + completion["timeout"] = 12 + url = completion.instance_url() + assert ( + url + == "/openai/deployments/test_engine/completions/test_id?api-version=2021-11-01-preview&timeout=12" + ) + + +@pytest.mark.url +def test_completions_url_composition_instance_url_timeout_openai() -> None: + completion = Completion(id="test_id", engine="test_engine", api_type="open_ai") + completion["timeout"] = 12 + url = completion.instance_url() + assert url == "/engines/test_engine/completions/test_id?timeout=12" + + +@pytest.mark.url +def test_engine_search_url_composition_azure() -> None: + engine = Engine(id="test_id", api_type="azure", api_version="2021-11-01-preview") + assert engine.api_type == "azure" + assert engine.typed_api_type == ApiType.AZURE + url = engine.instance_url("test_operation") + assert ( + url + == "/openai/deployments/test_id/test_operation?api-version=2021-11-01-preview" + ) + + +@pytest.mark.url +def test_engine_search_url_composition_azure_ad() -> None: + engine = Engine(id="test_id", api_type="azure_ad", api_version="2021-11-01-preview") + assert engine.api_type == "azure_ad" + assert engine.typed_api_type == ApiType.AZURE_AD + url = engine.instance_url("test_operation") + assert ( + url + == "/openai/deployments/test_id/test_operation?api-version=2021-11-01-preview" + ) + + +@pytest.mark.url +def test_engine_search_url_composition_azure_no_version() -> None: + engine = Engine(id="test_id", api_type="azure", api_version=None) + assert engine.api_type == "azure" + assert engine.typed_api_type == ApiType.AZURE + with pytest.raises(Exception): + engine.instance_url("test_operation") + + +@pytest.mark.url +def test_engine_search_url_composition_azure_no_operation() -> None: + engine = Engine(id="test_id", api_type="azure", api_version="2021-11-01-preview") + assert engine.api_type == "azure" + assert engine.typed_api_type == ApiType.AZURE + assert ( + engine.instance_url() + == "/openai/engines/test_id?api-version=2021-11-01-preview" + ) + + +@pytest.mark.url +def test_engine_search_url_composition_default() -> None: + engine = Engine(id="test_id") + assert engine.api_type == None + assert engine.typed_api_type == ApiType.OPEN_AI + url = engine.instance_url() + assert url == "/engines/test_id" + + +@pytest.mark.url +def test_engine_search_url_composition_open_ai() -> None: + engine = Engine(id="test_id", api_type="open_ai") + assert engine.api_type == "open_ai" + assert engine.typed_api_type == ApiType.OPEN_AI + url = engine.instance_url() + assert url == "/engines/test_id" + + +@pytest.mark.url +def test_engine_search_url_composition_invalid_type() -> None: + engine = Engine(id="test_id", api_type="invalid") + assert engine.api_type == "invalid" + with pytest.raises(Exception): + assert engine.typed_api_type == ApiType.OPEN_AI + + +@pytest.mark.url +def test_engine_search_url_composition_invalid_search() -> None: + engine = Engine(id="test_id", api_type="invalid") + assert engine.api_type == "invalid" + with pytest.raises(Exception): + engine.search() diff --git a/lib/openai/tests/test_util.py b/lib/openai/tests/test_util.py new file mode 100644 index 0000000..d0ce0ac --- /dev/null +++ b/lib/openai/tests/test_util.py @@ -0,0 +1,30 @@ +from tempfile import NamedTemporaryFile + +import pytest + +import openai +from openai import util + + +@pytest.fixture(scope="function") +def api_key_file(): + saved_path = openai.api_key_path + try: + with NamedTemporaryFile(prefix="openai-api-key", mode="wt") as tmp: + openai.api_key_path = tmp.name + yield tmp + finally: + openai.api_key_path = saved_path + + +def test_openai_api_key_path(api_key_file) -> None: + print("sk-foo", file=api_key_file) + api_key_file.flush() + assert util.default_api_key() == "sk-foo" + + +def test_openai_api_key_path_with_malformed_key(api_key_file) -> None: + print("malformed-api-key", file=api_key_file) + api_key_file.flush() + with pytest.raises(ValueError, match="Malformed API key"): + util.default_api_key() diff --git a/lib/openai/upload_progress.py b/lib/openai/upload_progress.py new file mode 100644 index 0000000..e4da62a --- /dev/null +++ b/lib/openai/upload_progress.py @@ -0,0 +1,52 @@ +import io + + +class CancelledError(Exception): + def __init__(self, msg): + self.msg = msg + Exception.__init__(self, msg) + + def __str__(self): + return self.msg + + __repr__ = __str__ + + +class BufferReader(io.BytesIO): + def __init__(self, buf=b"", desc=None): + self._len = len(buf) + io.BytesIO.__init__(self, buf) + self._progress = 0 + self._callback = progress(len(buf), desc=desc) + + def __len__(self): + return self._len + + def read(self, n=-1): + chunk = io.BytesIO.read(self, n) + self._progress += len(chunk) + if self._callback: + try: + self._callback(self._progress) + except Exception as e: # catches exception from the callback + raise CancelledError("The upload was cancelled: {}".format(e)) + return chunk + + +def progress(total, desc): + import tqdm # type: ignore + + meter = tqdm.tqdm(total=total, unit_scale=True, desc=desc) + + def incr(progress): + meter.n = progress + if progress == total: + meter.close() + else: + meter.refresh() + + return incr + + +def MB(i): + return int(i // 1024**2) diff --git a/lib/openai/util.py b/lib/openai/util.py new file mode 100644 index 0000000..56dfc2e --- /dev/null +++ b/lib/openai/util.py @@ -0,0 +1,188 @@ +import logging +import os +import re +import sys +from enum import Enum +from typing import Optional + +import openai + +OPENAI_LOG = os.environ.get("OPENAI_LOG") + +logger = logging.getLogger("openai") + +__all__ = [ + "log_info", + "log_debug", + "log_warn", + "logfmt", +] + +api_key_to_header = ( + lambda api, key: {"Authorization": f"Bearer {key}"} + if api in (ApiType.OPEN_AI, ApiType.AZURE_AD) + else {"api-key": f"{key}"} +) + + +class ApiType(Enum): + AZURE = 1 + OPEN_AI = 2 + AZURE_AD = 3 + + @staticmethod + def from_str(label): + if label.lower() == "azure": + return ApiType.AZURE + elif label.lower() in ("azure_ad", "azuread"): + return ApiType.AZURE_AD + elif label.lower() in ("open_ai", "openai"): + return ApiType.OPEN_AI + else: + raise openai.error.InvalidAPIType( + "The API type provided in invalid. Please select one of the supported API types: 'azure', 'azure_ad', 'open_ai'" + ) + + +def _console_log_level(): + if openai.log in ["debug", "info"]: + return openai.log + elif OPENAI_LOG in ["debug", "info"]: + return OPENAI_LOG + else: + return None + + +def log_debug(message, **params): + msg = logfmt(dict(message=message, **params)) + if _console_log_level() == "debug": + print(msg, file=sys.stderr) + logger.debug(msg) + + +def log_info(message, **params): + msg = logfmt(dict(message=message, **params)) + if _console_log_level() in ["debug", "info"]: + print(msg, file=sys.stderr) + logger.info(msg) + + +def log_warn(message, **params): + msg = logfmt(dict(message=message, **params)) + print(msg, file=sys.stderr) + logger.warn(msg) + + +def logfmt(props): + def fmt(key, val): + # Handle case where val is a bytes or bytesarray + if hasattr(val, "decode"): + val = val.decode("utf-8") + # Check if val is already a string to avoid re-encoding into ascii. + if not isinstance(val, str): + val = str(val) + if re.search(r"\s", val): + val = repr(val) + # key should already be a string + if re.search(r"\s", key): + key = repr(key) + return "{key}={val}".format(key=key, val=val) + + return " ".join([fmt(key, val) for key, val in sorted(props.items())]) + + +def get_object_classes(): + # This is here to avoid a circular dependency + from openai.object_classes import OBJECT_CLASSES + + return OBJECT_CLASSES + + +def convert_to_openai_object( + resp, + api_key=None, + api_version=None, + organization=None, + engine=None, + plain_old_data=False, +): + # If we get a OpenAIResponse, we'll want to return a OpenAIObject. + + response_ms: Optional[int] = None + if isinstance(resp, openai.openai_response.OpenAIResponse): + organization = resp.organization + response_ms = resp.response_ms + resp = resp.data + + if plain_old_data: + return resp + elif isinstance(resp, list): + return [ + convert_to_openai_object( + i, api_key, api_version, organization, engine=engine + ) + for i in resp + ] + elif isinstance(resp, dict) and not isinstance( + resp, openai.openai_object.OpenAIObject + ): + resp = resp.copy() + klass_name = resp.get("object") + if isinstance(klass_name, str): + klass = get_object_classes().get( + klass_name, openai.openai_object.OpenAIObject + ) + else: + klass = openai.openai_object.OpenAIObject + + return klass.construct_from( + resp, + api_key=api_key, + api_version=api_version, + organization=organization, + response_ms=response_ms, + engine=engine, + ) + else: + return resp + + +def convert_to_dict(obj): + """Converts a OpenAIObject back to a regular dict. + + Nested OpenAIObjects are also converted back to regular dicts. + + :param obj: The OpenAIObject to convert. + + :returns: The OpenAIObject as a dict. + """ + if isinstance(obj, list): + return [convert_to_dict(i) for i in obj] + # This works by virtue of the fact that OpenAIObjects _are_ dicts. The dict + # comprehension returns a regular dict and recursively applies the + # conversion to each value. + elif isinstance(obj, dict): + return {k: convert_to_dict(v) for k, v in obj.items()} + else: + return obj + + +def merge_dicts(x, y): + z = x.copy() + z.update(y) + return z + + +def default_api_key() -> str: + if openai.api_key_path: + with open(openai.api_key_path, "rt") as k: + api_key = k.read().strip() + if not api_key.startswith("sk-"): + raise ValueError(f"Malformed API key in {openai.api_key_path}.") + return api_key + elif openai.api_key is not None: + return openai.api_key + else: + raise openai.error.AuthenticationError( + "No API key provided. You can set your API key in code using 'openai.api_key = ', or you can set the environment variable OPENAI_API_KEY=). If your API key is stored in a file, you can point the openai module at it with 'openai.api_key_path = '. You can generate API keys in the OpenAI web interface. See https://onboard.openai.com for details, or email support@openai.com if you have any questions." + ) diff --git a/lib/openai/validators.py b/lib/openai/validators.py new file mode 100644 index 0000000..b15e59b --- /dev/null +++ b/lib/openai/validators.py @@ -0,0 +1,841 @@ +import os +import sys +from typing import Any, Callable, NamedTuple, Optional + +from openai.datalib import pandas as pd, assert_has_pandas + + +class Remediation(NamedTuple): + name: str + immediate_msg: Optional[str] = None + necessary_msg: Optional[str] = None + necessary_fn: Optional[Callable[[Any], Any]] = None + optional_msg: Optional[str] = None + optional_fn: Optional[Callable[[Any], Any]] = None + error_msg: Optional[str] = None + + +def num_examples_validator(df): + """ + This validator will only print out the number of examples and recommend to the user to increase the number of examples if less than 100. + """ + MIN_EXAMPLES = 100 + optional_suggestion = ( + "" + if len(df) >= MIN_EXAMPLES + else ". In general, we recommend having at least a few hundred examples. We've found that performance tends to linearly increase for every doubling of the number of examples" + ) + immediate_msg = ( + f"\n- Your file contains {len(df)} prompt-completion pairs{optional_suggestion}" + ) + return Remediation(name="num_examples", immediate_msg=immediate_msg) + + +def necessary_column_validator(df, necessary_column): + """ + This validator will ensure that the necessary column is present in the dataframe. + """ + + def lower_case_column(df, column): + cols = [c for c in df.columns if str(c).lower() == column] + df.rename(columns={cols[0]: column.lower()}, inplace=True) + return df + + immediate_msg = None + necessary_fn = None + necessary_msg = None + error_msg = None + + if necessary_column not in df.columns: + if necessary_column in [str(c).lower() for c in df.columns]: + + def lower_case_column_creator(df): + return lower_case_column(df, necessary_column) + + necessary_fn = lower_case_column_creator + immediate_msg = ( + f"\n- The `{necessary_column}` column/key should be lowercase" + ) + necessary_msg = f"Lower case column name to `{necessary_column}`" + else: + error_msg = f"`{necessary_column}` column/key is missing. Please make sure you name your columns/keys appropriately, then retry" + + return Remediation( + name="necessary_column", + immediate_msg=immediate_msg, + necessary_msg=necessary_msg, + necessary_fn=necessary_fn, + error_msg=error_msg, + ) + + +def additional_column_validator(df, fields=["prompt", "completion"]): + """ + This validator will remove additional columns from the dataframe. + """ + additional_columns = [] + necessary_msg = None + immediate_msg = None + necessary_fn = None + if len(df.columns) > 2: + additional_columns = [c for c in df.columns if c not in fields] + warn_message = "" + for ac in additional_columns: + dups = [c for c in additional_columns if ac in c] + if len(dups) > 0: + warn_message += f"\n WARNING: Some of the additional columns/keys contain `{ac}` in their name. These will be ignored, and the column/key `{ac}` will be used instead. This could also result from a duplicate column/key in the provided file." + immediate_msg = f"\n- The input file should contain exactly two columns/keys per row. Additional columns/keys present are: {additional_columns}{warn_message}" + necessary_msg = f"Remove additional columns/keys: {additional_columns}" + + def necessary_fn(x): + return x[fields] + + return Remediation( + name="additional_column", + immediate_msg=immediate_msg, + necessary_msg=necessary_msg, + necessary_fn=necessary_fn, + ) + + +def non_empty_field_validator(df, field="completion"): + """ + This validator will ensure that no completion is empty. + """ + necessary_msg = None + necessary_fn = None + immediate_msg = None + + if df[field].apply(lambda x: x == "").any() or df[field].isnull().any(): + empty_rows = (df[field] == "") | (df[field].isnull()) + empty_indexes = df.reset_index().index[empty_rows].tolist() + immediate_msg = f"\n- `{field}` column/key should not contain empty strings. These are rows: {empty_indexes}" + + def necessary_fn(x): + return x[x[field] != ""].dropna(subset=[field]) + + necessary_msg = f"Remove {len(empty_indexes)} rows with empty {field}s" + return Remediation( + name=f"empty_{field}", + immediate_msg=immediate_msg, + necessary_msg=necessary_msg, + necessary_fn=necessary_fn, + ) + + +def duplicated_rows_validator(df, fields=["prompt", "completion"]): + """ + This validator will suggest to the user to remove duplicate rows if they exist. + """ + duplicated_rows = df.duplicated(subset=fields) + duplicated_indexes = df.reset_index().index[duplicated_rows].tolist() + immediate_msg = None + optional_msg = None + optional_fn = None + + if len(duplicated_indexes) > 0: + immediate_msg = f"\n- There are {len(duplicated_indexes)} duplicated {'-'.join(fields)} sets. These are rows: {duplicated_indexes}" + optional_msg = f"Remove {len(duplicated_indexes)} duplicate rows" + + def optional_fn(x): + return x.drop_duplicates(subset=fields) + + return Remediation( + name="duplicated_rows", + immediate_msg=immediate_msg, + optional_msg=optional_msg, + optional_fn=optional_fn, + ) + + +def long_examples_validator(df): + """ + This validator will suggest to the user to remove examples that are too long. + """ + immediate_msg = None + optional_msg = None + optional_fn = None + + ft_type = infer_task_type(df) + if ft_type != "open-ended generation": + def get_long_indexes(d): + long_examples = d.apply( + lambda x: len(x.prompt) + len(x.completion) > 10000, axis=1 + ) + return d.reset_index().index[long_examples].tolist() + + long_indexes = get_long_indexes(df) + + if len(long_indexes) > 0: + immediate_msg = f"\n- There are {len(long_indexes)} examples that are very long. These are rows: {long_indexes}\nFor conditional generation, and for classification the examples shouldn't be longer than 2048 tokens." + optional_msg = f"Remove {len(long_indexes)} long examples" + + def optional_fn(x): + + long_indexes_to_drop = get_long_indexes(x) + if long_indexes != long_indexes_to_drop: + sys.stdout.write(f"The indices of the long examples has changed as a result of a previously applied recommendation.\nThe {len(long_indexes_to_drop)} long examples to be dropped are now at the following indices: {long_indexes_to_drop}\n") + return x.drop(long_indexes_to_drop) + + return Remediation( + name="long_examples", + immediate_msg=immediate_msg, + optional_msg=optional_msg, + optional_fn=optional_fn, + ) + + +def common_prompt_suffix_validator(df): + """ + This validator will suggest to add a common suffix to the prompt if one doesn't already exist in case of classification or conditional generation. + """ + error_msg = None + immediate_msg = None + optional_msg = None + optional_fn = None + + # Find a suffix which is not contained within the prompt otherwise + suggested_suffix = "\n\n### =>\n\n" + suffix_options = [ + " ->", + "\n\n###\n\n", + "\n\n===\n\n", + "\n\n---\n\n", + "\n\n===>\n\n", + "\n\n--->\n\n", + ] + for suffix_option in suffix_options: + if suffix_option == " ->": + if df.prompt.str.contains("\n").any(): + continue + if df.prompt.str.contains(suffix_option, regex=False).any(): + continue + suggested_suffix = suffix_option + break + display_suggested_suffix = suggested_suffix.replace("\n", "\\n") + + ft_type = infer_task_type(df) + if ft_type == "open-ended generation": + return Remediation(name="common_suffix") + + def add_suffix(x, suffix): + x["prompt"] += suffix + return x + + common_suffix = get_common_xfix(df.prompt, xfix="suffix") + if (df.prompt == common_suffix).all(): + error_msg = f"All prompts are identical: `{common_suffix}`\nConsider leaving the prompts blank if you want to do open-ended generation, otherwise ensure prompts are different" + return Remediation(name="common_suffix", error_msg=error_msg) + + if common_suffix != "": + common_suffix_new_line_handled = common_suffix.replace("\n", "\\n") + immediate_msg = ( + f"\n- All prompts end with suffix `{common_suffix_new_line_handled}`" + ) + if len(common_suffix) > 10: + immediate_msg += f". This suffix seems very long. Consider replacing with a shorter suffix, such as `{display_suggested_suffix}`" + if ( + df.prompt.str[: -len(common_suffix)] + .str.contains(common_suffix, regex=False) + .any() + ): + immediate_msg += f"\n WARNING: Some of your prompts contain the suffix `{common_suffix}` more than once. We strongly suggest that you review your prompts and add a unique suffix" + + else: + immediate_msg = "\n- Your data does not contain a common separator at the end of your prompts. Having a separator string appended to the end of the prompt makes it clearer to the fine-tuned model where the completion should begin. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more detail and examples. If you intend to do open-ended generation, then you should leave the prompts empty" + + if common_suffix == "": + optional_msg = ( + f"Add a suffix separator `{display_suggested_suffix}` to all prompts" + ) + + def optional_fn(x): + return add_suffix(x, suggested_suffix) + + return Remediation( + name="common_completion_suffix", + immediate_msg=immediate_msg, + optional_msg=optional_msg, + optional_fn=optional_fn, + error_msg=error_msg, + ) + + +def common_prompt_prefix_validator(df): + """ + This validator will suggest to remove a common prefix from the prompt if a long one exist. + """ + MAX_PREFIX_LEN = 12 + + immediate_msg = None + optional_msg = None + optional_fn = None + + common_prefix = get_common_xfix(df.prompt, xfix="prefix") + if common_prefix == "": + return Remediation(name="common_prefix") + + def remove_common_prefix(x, prefix): + x["prompt"] = x["prompt"].str[len(prefix) :] + return x + + if (df.prompt == common_prefix).all(): + # already handled by common_suffix_validator + return Remediation(name="common_prefix") + + if common_prefix != "": + immediate_msg = f"\n- All prompts start with prefix `{common_prefix}`" + if MAX_PREFIX_LEN < len(common_prefix): + immediate_msg += ". Fine-tuning doesn't require the instruction specifying the task, or a few-shot example scenario. Most of the time you should only add the input data into the prompt, and the desired output into the completion" + optional_msg = f"Remove prefix `{common_prefix}` from all prompts" + + def optional_fn(x): + return remove_common_prefix(x, common_prefix) + + return Remediation( + name="common_prompt_prefix", + immediate_msg=immediate_msg, + optional_msg=optional_msg, + optional_fn=optional_fn, + ) + + +def common_completion_prefix_validator(df): + """ + This validator will suggest to remove a common prefix from the completion if a long one exist. + """ + MAX_PREFIX_LEN = 5 + + common_prefix = get_common_xfix(df.completion, xfix="prefix") + ws_prefix = len(common_prefix) > 0 and common_prefix[0] == " " + if len(common_prefix) < MAX_PREFIX_LEN: + return Remediation(name="common_prefix") + + def remove_common_prefix(x, prefix, ws_prefix): + x["completion"] = x["completion"].str[len(prefix) :] + if ws_prefix: + # keep the single whitespace as prefix + x["completion"] = " " + x["completion"] + return x + + if (df.completion == common_prefix).all(): + # already handled by common_suffix_validator + return Remediation(name="common_prefix") + + immediate_msg = f"\n- All completions start with prefix `{common_prefix}`. Most of the time you should only add the output data into the completion, without any prefix" + optional_msg = f"Remove prefix `{common_prefix}` from all completions" + + def optional_fn(x): + return remove_common_prefix(x, common_prefix, ws_prefix) + + return Remediation( + name="common_completion_prefix", + immediate_msg=immediate_msg, + optional_msg=optional_msg, + optional_fn=optional_fn, + ) + + +def common_completion_suffix_validator(df): + """ + This validator will suggest to add a common suffix to the completion if one doesn't already exist in case of classification or conditional generation. + """ + error_msg = None + immediate_msg = None + optional_msg = None + optional_fn = None + + ft_type = infer_task_type(df) + if ft_type == "open-ended generation" or ft_type == "classification": + return Remediation(name="common_suffix") + + common_suffix = get_common_xfix(df.completion, xfix="suffix") + if (df.completion == common_suffix).all(): + error_msg = f"All completions are identical: `{common_suffix}`\nEnsure completions are different, otherwise the model will just repeat `{common_suffix}`" + return Remediation(name="common_suffix", error_msg=error_msg) + + # Find a suffix which is not contained within the completion otherwise + suggested_suffix = " [END]" + suffix_options = [ + "\n", + ".", + " END", + "***", + "+++", + "&&&", + "$$$", + "@@@", + "%%%", + ] + for suffix_option in suffix_options: + if df.completion.str.contains(suffix_option, regex=False).any(): + continue + suggested_suffix = suffix_option + break + display_suggested_suffix = suggested_suffix.replace("\n", "\\n") + + def add_suffix(x, suffix): + x["completion"] += suffix + return x + + if common_suffix != "": + common_suffix_new_line_handled = common_suffix.replace("\n", "\\n") + immediate_msg = ( + f"\n- All completions end with suffix `{common_suffix_new_line_handled}`" + ) + if len(common_suffix) > 10: + immediate_msg += f". This suffix seems very long. Consider replacing with a shorter suffix, such as `{display_suggested_suffix}`" + if ( + df.completion.str[: -len(common_suffix)] + .str.contains(common_suffix, regex=False) + .any() + ): + immediate_msg += f"\n WARNING: Some of your completions contain the suffix `{common_suffix}` more than once. We suggest that you review your completions and add a unique ending" + + else: + immediate_msg = "\n- Your data does not contain a common ending at the end of your completions. Having a common ending string appended to the end of the completion makes it clearer to the fine-tuned model where the completion should end. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more detail and examples." + + if common_suffix == "": + optional_msg = ( + f"Add a suffix ending `{display_suggested_suffix}` to all completions" + ) + + def optional_fn(x): + return add_suffix(x, suggested_suffix) + + return Remediation( + name="common_completion_suffix", + immediate_msg=immediate_msg, + optional_msg=optional_msg, + optional_fn=optional_fn, + error_msg=error_msg, + ) + + +def completions_space_start_validator(df): + """ + This validator will suggest to add a space at the start of the completion if it doesn't already exist. This helps with tokenization. + """ + + def add_space_start(x): + x["completion"] = x["completion"].apply( + lambda x: ("" if x[0] == " " else " ") + x + ) + return x + + optional_msg = None + optional_fn = None + immediate_msg = None + + if df.completion.str[:1].nunique() != 1 or df.completion.values[0][0] != " ": + immediate_msg = "\n- The completion should start with a whitespace character (` `). This tends to produce better results due to the tokenization we use. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more details" + optional_msg = "Add a whitespace character to the beginning of the completion" + optional_fn = add_space_start + return Remediation( + name="completion_space_start", + immediate_msg=immediate_msg, + optional_msg=optional_msg, + optional_fn=optional_fn, + ) + + +def lower_case_validator(df, column): + """ + This validator will suggest to lowercase the column values, if more than a third of letters are uppercase. + """ + + def lower_case(x): + x[column] = x[column].str.lower() + return x + + count_upper = ( + df[column] + .apply(lambda x: sum(1 for c in x if c.isalpha() and c.isupper())) + .sum() + ) + count_lower = ( + df[column] + .apply(lambda x: sum(1 for c in x if c.isalpha() and c.islower())) + .sum() + ) + + if count_upper * 2 > count_lower: + return Remediation( + name="lower_case", + immediate_msg=f"\n- More than a third of your `{column}` column/key is uppercase. Uppercase {column}s tends to perform worse than a mixture of case encountered in normal language. We recommend to lower case the data if that makes sense in your domain. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more details", + optional_msg=f"Lowercase all your data in column/key `{column}`", + optional_fn=lower_case, + ) + + +def read_any_format(fname, fields=["prompt", "completion"]): + """ + This function will read a file saved in .csv, .json, .txt, .xlsx or .tsv format using pandas. + - for .xlsx it will read the first sheet + - for .txt it will assume completions and split on newline + """ + assert_has_pandas() + remediation = None + necessary_msg = None + immediate_msg = None + error_msg = None + df = None + + if os.path.isfile(fname): + try: + if fname.lower().endswith(".csv") or fname.lower().endswith(".tsv"): + file_extension_str, separator = ( + ("CSV", ",") if fname.lower().endswith(".csv") else ("TSV", "\t") + ) + immediate_msg = f"\n- Based on your file extension, your file is formatted as a {file_extension_str} file" + necessary_msg = ( + f"Your format `{file_extension_str}` will be converted to `JSONL`" + ) + df = pd.read_csv(fname, sep=separator, dtype=str).fillna("") + elif fname.lower().endswith(".xlsx"): + immediate_msg = "\n- Based on your file extension, your file is formatted as an Excel file" + necessary_msg = "Your format `XLSX` will be converted to `JSONL`" + xls = pd.ExcelFile(fname) + sheets = xls.sheet_names + if len(sheets) > 1: + immediate_msg += "\n- Your Excel file contains more than one sheet. Please either save as csv or ensure all data is present in the first sheet. WARNING: Reading only the first sheet..." + df = pd.read_excel(fname, dtype=str).fillna("") + elif fname.lower().endswith(".txt"): + immediate_msg = ( + "\n- Based on your file extension, you provided a text file" + ) + necessary_msg = "Your format `TXT` will be converted to `JSONL`" + with open(fname, "r") as f: + content = f.read() + df = pd.DataFrame( + [["", line] for line in content.split("\n")], + columns=fields, + dtype=str, + ).fillna("") + elif fname.lower().endswith(".jsonl"): + df = pd.read_json(fname, lines=True, dtype=str).fillna("") + if len(df) == 1: + # this is NOT what we expect for a .jsonl file + immediate_msg = "\n- Your JSONL file appears to be in a JSON format. Your file will be converted to JSONL format" + necessary_msg = "Your format `JSON` will be converted to `JSONL`" + df = pd.read_json(fname, dtype=str).fillna("") + else: + pass # this is what we expect for a .jsonl file + elif fname.lower().endswith(".json"): + df = pd.read_json(fname, lines=True, dtype=str).fillna("") + if len(df) == 1: + # this is what we expect for a .json file + df = pd.read_json(fname, dtype=str).fillna("") + else: + # this is NOT what we expect for a .json file + immediate_msg = "\n- Your JSON file appears to be in a JSONL format. Your file will be converted to JSONL format" + necessary_msg = "Your format `JSON` will be converted to `JSONL`" + else: + error_msg = "Your file must have one of the following extensions: .CSV, .TSV, .XLSX, .TXT, .JSON or .JSONL" + if "." in fname: + error_msg += f" Your file `{fname}` ends with the extension `.{fname.split('.')[-1]}` which is not supported." + else: + error_msg += f" Your file `{fname}` is missing a file extension." + + except (ValueError, TypeError): + file_extension_str = fname.split(".")[-1].upper() + error_msg = f"Your file `{fname}` does not appear to be in valid {file_extension_str} format. Please ensure your file is formatted as a valid {file_extension_str} file." + + else: + error_msg = f"File {fname} does not exist." + + remediation = Remediation( + name="read_any_format", + necessary_msg=necessary_msg, + immediate_msg=immediate_msg, + error_msg=error_msg, + ) + return df, remediation + + +def format_inferrer_validator(df): + """ + This validator will infer the likely fine-tuning format of the data, and display it to the user if it is classification. + It will also suggest to use ada and explain train/validation split benefits. + """ + ft_type = infer_task_type(df) + immediate_msg = None + if ft_type == "classification": + immediate_msg = f"\n- Based on your data it seems like you're trying to fine-tune a model for {ft_type}\n- For classification, we recommend you try one of the faster and cheaper models, such as `ada`\n- For classification, you can estimate the expected model performance by keeping a held out dataset, which is not used for training" + return Remediation(name="num_examples", immediate_msg=immediate_msg) + + +def apply_necessary_remediation(df, remediation): + """ + This function will apply a necessary remediation to a dataframe, or print an error message if one exists. + """ + if remediation.error_msg is not None: + sys.stderr.write( + f"\n\nERROR in {remediation.name} validator: {remediation.error_msg}\n\nAborting..." + ) + sys.exit(1) + if remediation.immediate_msg is not None: + sys.stdout.write(remediation.immediate_msg) + if remediation.necessary_fn is not None: + df = remediation.necessary_fn(df) + return df + + +def accept_suggestion(input_text, auto_accept): + sys.stdout.write(input_text) + if auto_accept: + sys.stdout.write("Y\n") + return True + return input().lower() != "n" + + +def apply_optional_remediation(df, remediation, auto_accept): + """ + This function will apply an optional remediation to a dataframe, based on the user input. + """ + optional_applied = False + input_text = f"- [Recommended] {remediation.optional_msg} [Y/n]: " + if remediation.optional_msg is not None: + if accept_suggestion(input_text, auto_accept): + df = remediation.optional_fn(df) + optional_applied = True + if remediation.necessary_msg is not None: + sys.stdout.write(f"- [Necessary] {remediation.necessary_msg}\n") + return df, optional_applied + + +def estimate_fine_tuning_time(df): + """ + Estimate the time it'll take to fine-tune the dataset + """ + ft_format = infer_task_type(df) + expected_time = 1.0 + if ft_format == "classification": + num_examples = len(df) + expected_time = num_examples * 1.44 + else: + size = df.memory_usage(index=True).sum() + expected_time = size * 0.0515 + + def format_time(time): + if time < 60: + return f"{round(time, 2)} seconds" + elif time < 3600: + return f"{round(time / 60, 2)} minutes" + elif time < 86400: + return f"{round(time / 3600, 2)} hours" + else: + return f"{round(time / 86400, 2)} days" + + time_string = format_time(expected_time + 140) + sys.stdout.write( + f"Once your model starts training, it'll approximately take {time_string} to train a `curie` model, and less for `ada` and `babbage`. Queue will approximately take half an hour per job ahead of you.\n" + ) + + +def get_outfnames(fname, split): + suffixes = ["_train", "_valid"] if split else [""] + i = 0 + while True: + index_suffix = f" ({i})" if i > 0 else "" + candidate_fnames = [ + os.path.splitext(fname)[0] + "_prepared" + suffix + index_suffix + ".jsonl" + for suffix in suffixes + ] + if not any(os.path.isfile(f) for f in candidate_fnames): + return candidate_fnames + i += 1 + + +def get_classification_hyperparams(df): + n_classes = df.completion.nunique() + pos_class = None + if n_classes == 2: + pos_class = df.completion.value_counts().index[0] + return n_classes, pos_class + + +def write_out_file(df, fname, any_remediations, auto_accept): + """ + This function will write out a dataframe to a file, if the user would like to proceed, and also offer a fine-tuning command with the newly created file. + For classification it will optionally ask the user if they would like to split the data into train/valid files, and modify the suggested command to include the valid set. + """ + ft_format = infer_task_type(df) + common_prompt_suffix = get_common_xfix(df.prompt, xfix="suffix") + common_completion_suffix = get_common_xfix(df.completion, xfix="suffix") + + split = False + input_text = "- [Recommended] Would you like to split into training and validation set? [Y/n]: " + if ft_format == "classification": + if accept_suggestion(input_text, auto_accept): + split = True + + additional_params = "" + common_prompt_suffix_new_line_handled = common_prompt_suffix.replace("\n", "\\n") + common_completion_suffix_new_line_handled = common_completion_suffix.replace( + "\n", "\\n" + ) + optional_ending_string = ( + f' Make sure to include `stop=["{common_completion_suffix_new_line_handled}"]` so that the generated texts ends at the expected place.' + if len(common_completion_suffix_new_line_handled) > 0 + else "" + ) + + input_text = "\n\nYour data will be written to a new JSONL file. Proceed [Y/n]: " + + if not any_remediations and not split: + sys.stdout.write( + f'\nYou can use your file for fine-tuning:\n> openai api fine_tunes.create -t "{fname}"{additional_params}\n\nAfter you’ve fine-tuned a model, remember that your prompt has to end with the indicator string `{common_prompt_suffix_new_line_handled}` for the model to start generating completions, rather than continuing with the prompt.{optional_ending_string}\n' + ) + estimate_fine_tuning_time(df) + + elif accept_suggestion(input_text, auto_accept): + fnames = get_outfnames(fname, split) + if split: + assert len(fnames) == 2 and "train" in fnames[0] and "valid" in fnames[1] + MAX_VALID_EXAMPLES = 1000 + n_train = max(len(df) - MAX_VALID_EXAMPLES, int(len(df) * 0.8)) + df_train = df.sample(n=n_train, random_state=42) + df_valid = df.drop(df_train.index) + df_train[["prompt", "completion"]].to_json( + fnames[0], lines=True, orient="records", force_ascii=False + ) + df_valid[["prompt", "completion"]].to_json( + fnames[1], lines=True, orient="records", force_ascii=False + ) + + n_classes, pos_class = get_classification_hyperparams(df) + additional_params += " --compute_classification_metrics" + if n_classes == 2: + additional_params += f' --classification_positive_class "{pos_class}"' + else: + additional_params += f" --classification_n_classes {n_classes}" + else: + assert len(fnames) == 1 + df[["prompt", "completion"]].to_json( + fnames[0], lines=True, orient="records", force_ascii=False + ) + + # Add -v VALID_FILE if we split the file into train / valid + files_string = ("s" if split else "") + " to `" + ("` and `".join(fnames)) + valid_string = f' -v "{fnames[1]}"' if split else "" + separator_reminder = ( + "" + if len(common_prompt_suffix_new_line_handled) == 0 + else f"After you’ve fine-tuned a model, remember that your prompt has to end with the indicator string `{common_prompt_suffix_new_line_handled}` for the model to start generating completions, rather than continuing with the prompt." + ) + sys.stdout.write( + f'\nWrote modified file{files_string}`\nFeel free to take a look!\n\nNow use that file when fine-tuning:\n> openai api fine_tunes.create -t "{fnames[0]}"{valid_string}{additional_params}\n\n{separator_reminder}{optional_ending_string}\n' + ) + estimate_fine_tuning_time(df) + else: + sys.stdout.write("Aborting... did not write the file\n") + + +def infer_task_type(df): + """ + Infer the likely fine-tuning task type from the data + """ + CLASSIFICATION_THRESHOLD = 3 # min_average instances of each class + if sum(df.prompt.str.len()) == 0: + return "open-ended generation" + + if len(df.completion.unique()) < len(df) / CLASSIFICATION_THRESHOLD: + return "classification" + + return "conditional generation" + + +def get_common_xfix(series, xfix="suffix"): + """ + Finds the longest common suffix or prefix of all the values in a series + """ + common_xfix = "" + while True: + common_xfixes = ( + series.str[-(len(common_xfix) + 1) :] + if xfix == "suffix" + else series.str[: len(common_xfix) + 1] + ) # first few or last few characters + if ( + common_xfixes.nunique() != 1 + ): # we found the character at which we don't have a unique xfix anymore + break + elif ( + common_xfix == common_xfixes.values[0] + ): # the entire first row is a prefix of every other row + break + else: # the first or last few characters are still common across all rows - let's try to add one more + common_xfix = common_xfixes.values[0] + return common_xfix + + +def get_validators(): + return [ + num_examples_validator, + lambda x: necessary_column_validator(x, "prompt"), + lambda x: necessary_column_validator(x, "completion"), + additional_column_validator, + non_empty_field_validator, + format_inferrer_validator, + duplicated_rows_validator, + long_examples_validator, + lambda x: lower_case_validator(x, "prompt"), + lambda x: lower_case_validator(x, "completion"), + common_prompt_suffix_validator, + common_prompt_prefix_validator, + common_completion_prefix_validator, + common_completion_suffix_validator, + completions_space_start_validator, + ] + + +def apply_validators( + df, + fname, + remediation, + validators, + auto_accept, + write_out_file_func, +): + optional_remediations = [] + if remediation is not None: + optional_remediations.append(remediation) + for validator in validators: + remediation = validator(df) + if remediation is not None: + optional_remediations.append(remediation) + df = apply_necessary_remediation(df, remediation) + + any_optional_or_necessary_remediations = any( + [ + remediation + for remediation in optional_remediations + if remediation.optional_msg is not None + or remediation.necessary_msg is not None + ] + ) + any_necessary_applied = any( + [ + remediation + for remediation in optional_remediations + if remediation.necessary_msg is not None + ] + ) + any_optional_applied = False + + if any_optional_or_necessary_remediations: + sys.stdout.write( + "\n\nBased on the analysis we will perform the following actions:\n" + ) + for remediation in optional_remediations: + df, optional_applied = apply_optional_remediation( + df, remediation, auto_accept + ) + any_optional_applied = any_optional_applied or optional_applied + else: + sys.stdout.write("\n\nNo remediations found.\n") + + any_optional_or_necessary_applied = any_optional_applied or any_necessary_applied + + write_out_file_func(df, fname, any_optional_or_necessary_applied, auto_accept) diff --git a/lib/openai/version.py b/lib/openai/version.py new file mode 100644 index 0000000..cba8d89 --- /dev/null +++ b/lib/openai/version.py @@ -0,0 +1 @@ +VERSION = "0.27.2" diff --git a/lib/openai/wandb_logger.py b/lib/openai/wandb_logger.py new file mode 100644 index 0000000..ba650d1 --- /dev/null +++ b/lib/openai/wandb_logger.py @@ -0,0 +1,300 @@ +try: + import wandb + + WANDB_AVAILABLE = True +except: + WANDB_AVAILABLE = False + + +if WANDB_AVAILABLE: + import datetime + import io + import json + import re + from pathlib import Path + + from openai import File, FineTune + from openai.datalib import numpy as np + from openai.datalib import pandas as pd + + +class WandbLogger: + """ + Log fine-tunes to [Weights & Biases](https://wandb.me/openai-docs) + """ + + if not WANDB_AVAILABLE: + print("Logging requires wandb to be installed. Run `pip install wandb`.") + else: + _wandb_api = None + _logged_in = False + + @classmethod + def sync( + cls, + id=None, + n_fine_tunes=None, + project="GPT-3", + entity=None, + force=False, + **kwargs_wandb_init, + ): + """ + Sync fine-tunes to Weights & Biases. + :param id: The id of the fine-tune (optional) + :param n_fine_tunes: Number of most recent fine-tunes to log when an id is not provided. By default, every fine-tune is synced. + :param project: Name of the project where you're sending runs. By default, it is "GPT-3". + :param entity: Username or team name where you're sending runs. By default, your default entity is used, which is usually your username. + :param force: Forces logging and overwrite existing wandb run of the same fine-tune. + """ + + if not WANDB_AVAILABLE: + return + + if id: + fine_tune = FineTune.retrieve(id=id) + fine_tune.pop("events", None) + fine_tunes = [fine_tune] + + else: + # get list of fine_tune to log + fine_tunes = FineTune.list() + if not fine_tunes or fine_tunes.get("data") is None: + print("No fine-tune has been retrieved") + return + fine_tunes = fine_tunes["data"][ + -n_fine_tunes if n_fine_tunes is not None else None : + ] + + # log starting from oldest fine_tune + show_individual_warnings = ( + False if id is None and n_fine_tunes is None else True + ) + fine_tune_logged = [ + cls._log_fine_tune( + fine_tune, + project, + entity, + force, + show_individual_warnings, + **kwargs_wandb_init, + ) + for fine_tune in fine_tunes + ] + + if not show_individual_warnings and not any(fine_tune_logged): + print("No new successful fine-tunes were found") + + return "🎉 wandb sync completed successfully" + + @classmethod + def _log_fine_tune( + cls, + fine_tune, + project, + entity, + force, + show_individual_warnings, + **kwargs_wandb_init, + ): + fine_tune_id = fine_tune.get("id") + status = fine_tune.get("status") + + # check run completed successfully + if status != "succeeded": + if show_individual_warnings: + print( + f'Fine-tune {fine_tune_id} has the status "{status}" and will not be logged' + ) + return + + # check results are present + try: + results_id = fine_tune["result_files"][0]["id"] + results = File.download(id=results_id).decode("utf-8") + except: + if show_individual_warnings: + print(f"Fine-tune {fine_tune_id} has no results and will not be logged") + return + + # check run has not been logged already + run_path = f"{project}/{fine_tune_id}" + if entity is not None: + run_path = f"{entity}/{run_path}" + wandb_run = cls._get_wandb_run(run_path) + if wandb_run: + wandb_status = wandb_run.summary.get("status") + if show_individual_warnings: + if wandb_status == "succeeded": + print( + f"Fine-tune {fine_tune_id} has already been logged successfully at {wandb_run.url}" + ) + if not force: + print( + 'Use "--force" in the CLI or "force=True" in python if you want to overwrite previous run' + ) + else: + print( + f"A run for fine-tune {fine_tune_id} was previously created but didn't end successfully" + ) + if wandb_status != "succeeded" or force: + print( + f"A new wandb run will be created for fine-tune {fine_tune_id} and previous run will be overwritten" + ) + if wandb_status == "succeeded" and not force: + return + + # start a wandb run + wandb.init( + job_type="fine-tune", + config=cls._get_config(fine_tune), + project=project, + entity=entity, + name=fine_tune_id, + id=fine_tune_id, + **kwargs_wandb_init, + ) + + # log results + df_results = pd.read_csv(io.StringIO(results)) + for _, row in df_results.iterrows(): + metrics = {k: v for k, v in row.items() if not np.isnan(v)} + step = metrics.pop("step") + if step is not None: + step = int(step) + wandb.log(metrics, step=step) + fine_tuned_model = fine_tune.get("fine_tuned_model") + if fine_tuned_model is not None: + wandb.summary["fine_tuned_model"] = fine_tuned_model + + # training/validation files and fine-tune details + cls._log_artifacts(fine_tune, project, entity) + + # mark run as complete + wandb.summary["status"] = "succeeded" + + wandb.finish() + return True + + @classmethod + def _ensure_logged_in(cls): + if not cls._logged_in: + if wandb.login(): + cls._logged_in = True + else: + raise Exception("You need to log in to wandb") + + @classmethod + def _get_wandb_run(cls, run_path): + cls._ensure_logged_in() + try: + if cls._wandb_api is None: + cls._wandb_api = wandb.Api() + return cls._wandb_api.run(run_path) + except Exception: + return None + + @classmethod + def _get_wandb_artifact(cls, artifact_path): + cls._ensure_logged_in() + try: + if cls._wandb_api is None: + cls._wandb_api = wandb.Api() + return cls._wandb_api.artifact(artifact_path) + except Exception: + return None + + @classmethod + def _get_config(cls, fine_tune): + config = dict(fine_tune) + for key in ("training_files", "validation_files", "result_files"): + if config.get(key) and len(config[key]): + config[key] = config[key][0] + if config.get("created_at"): + config["created_at"] = datetime.datetime.fromtimestamp(config["created_at"]) + return config + + @classmethod + def _log_artifacts(cls, fine_tune, project, entity): + # training/validation files + training_file = ( + fine_tune["training_files"][0] + if fine_tune.get("training_files") and len(fine_tune["training_files"]) + else None + ) + validation_file = ( + fine_tune["validation_files"][0] + if fine_tune.get("validation_files") and len(fine_tune["validation_files"]) + else None + ) + for file, prefix, artifact_type in ( + (training_file, "train", "training_files"), + (validation_file, "valid", "validation_files"), + ): + if file is not None: + cls._log_artifact_inputs(file, prefix, artifact_type, project, entity) + + # fine-tune details + fine_tune_id = fine_tune.get("id") + artifact = wandb.Artifact( + "fine_tune_details", + type="fine_tune_details", + metadata=fine_tune, + ) + with artifact.new_file( + "fine_tune_details.json", mode="w", encoding="utf-8" + ) as f: + json.dump(fine_tune, f, indent=2) + wandb.run.log_artifact( + artifact, + aliases=["latest", fine_tune_id], + ) + + @classmethod + def _log_artifact_inputs(cls, file, prefix, artifact_type, project, entity): + file_id = file["id"] + filename = Path(file["filename"]).name + stem = Path(file["filename"]).stem + + # get input artifact + artifact_name = f"{prefix}-{filename}" + # sanitize name to valid wandb artifact name + artifact_name = re.sub(r"[^a-zA-Z0-9_\-.]", "_", artifact_name) + artifact_alias = file_id + artifact_path = f"{project}/{artifact_name}:{artifact_alias}" + if entity is not None: + artifact_path = f"{entity}/{artifact_path}" + artifact = cls._get_wandb_artifact(artifact_path) + + # create artifact if file not already logged previously + if artifact is None: + # get file content + try: + file_content = File.download(id=file_id).decode("utf-8") + except: + print( + f"File {file_id} could not be retrieved. Make sure you are allowed to download training/validation files" + ) + return + artifact = wandb.Artifact(artifact_name, type=artifact_type, metadata=file) + with artifact.new_file(filename, mode="w", encoding="utf-8") as f: + f.write(file_content) + + # create a Table + try: + table, n_items = cls._make_table(file_content) + artifact.add(table, stem) + wandb.config.update({f"n_{prefix}": n_items}) + artifact.metadata["items"] = n_items + except: + print(f"File {file_id} could not be read as a valid JSON file") + else: + # log number of items + wandb.config.update({f"n_{prefix}": artifact.metadata.get("items")}) + + wandb.run.use_artifact(artifact, aliases=["latest", artifact_alias]) + + @classmethod + def _make_table(cls, file_content): + df = pd.read_json(io.StringIO(file_content), orient="records", lines=True) + return wandb.Table(dataframe=df), len(df) diff --git a/lib/requests-2.28.2.dist-info/INSTALLER b/lib/requests-2.28.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/requests-2.28.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/requests-2.28.2.dist-info/LICENSE b/lib/requests-2.28.2.dist-info/LICENSE new file mode 100644 index 0000000..67db858 --- /dev/null +++ b/lib/requests-2.28.2.dist-info/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/lib/requests-2.28.2.dist-info/METADATA b/lib/requests-2.28.2.dist-info/METADATA new file mode 100644 index 0000000..b1ed44a --- /dev/null +++ b/lib/requests-2.28.2.dist-info/METADATA @@ -0,0 +1,121 @@ +Metadata-Version: 2.1 +Name: requests +Version: 2.28.2 +Summary: Python HTTP for Humans. +Home-page: https://requests.readthedocs.io +Author: Kenneth Reitz +Author-email: me@kennethreitz.org +License: Apache 2.0 +Project-URL: Documentation, https://requests.readthedocs.io +Project-URL: Source, https://github.com/psf/requests +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries +Requires-Python: >=3.7, <4 +Description-Content-Type: text/markdown +Requires-Dist: charset-normalizer (<4,>=2) +Requires-Dist: idna (<4,>=2.5) +Requires-Dist: urllib3 (<1.27,>=1.21.1) +Requires-Dist: certifi (>=2017.4.17) +Provides-Extra: security +Provides-Extra: socks +Requires-Dist: PySocks (!=1.5.7,>=1.5.6) ; extra == 'socks' +Provides-Extra: use_chardet_on_py3 +Requires-Dist: chardet (<6,>=3.0.2) ; extra == 'use_chardet_on_py3' + +# Requests + +**Requests** is a simple, yet elegant, HTTP library. + +```python +>>> import requests +>>> r = requests.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass')) +>>> r.status_code +200 +>>> r.headers['content-type'] +'application/json; charset=utf8' +>>> r.encoding +'utf-8' +>>> r.text +'{"authenticated": true, ...' +>>> r.json() +{'authenticated': True, ...} +``` + +Requests allows you to send HTTP/1.1 requests extremely easily. There’s no need to manually add query strings to your URLs, or to form-encode your `PUT` & `POST` data — but nowadays, just use the `json` method! + +Requests is one of the most downloaded Python packages today, pulling in around `30M downloads / week`— according to GitHub, Requests is currently [depended upon](https://github.com/psf/requests/network/dependents?package_id=UGFja2FnZS01NzA4OTExNg%3D%3D) by `1,000,000+` repositories. You may certainly put your trust in this code. + +[![Downloads](https://pepy.tech/badge/requests/month)](https://pepy.tech/project/requests) +[![Supported Versions](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests) +[![Contributors](https://img.shields.io/github/contributors/psf/requests.svg)](https://github.com/psf/requests/graphs/contributors) + +## Installing Requests and Supported Versions + +Requests is available on PyPI: + +```console +$ python -m pip install requests +``` + +Requests officially supports Python 3.7+. + +## Supported Features & Best–Practices + +Requests is ready for the demands of building robust and reliable HTTP–speaking applications, for the needs of today. + +- Keep-Alive & Connection Pooling +- International Domains and URLs +- Sessions with Cookie Persistence +- Browser-style TLS/SSL Verification +- Basic & Digest Authentication +- Familiar `dict`–like Cookies +- Automatic Content Decompression and Decoding +- Multi-part File Uploads +- SOCKS Proxy Support +- Connection Timeouts +- Streaming Downloads +- Automatic honoring of `.netrc` +- Chunked HTTP Requests + +## API Reference and User Guide available on [Read the Docs](https://requests.readthedocs.io) + +[![Read the Docs](https://raw.githubusercontent.com/psf/requests/main/ext/ss.png)](https://requests.readthedocs.io) + +## Cloning the repository + +When cloning the Requests repository, you may need to add the `-c +fetch.fsck.badTimezone=ignore` flag to avoid an error about a bad commit (see +[this issue](https://github.com/psf/requests/issues/2690) for more background): + +```shell +git clone -c fetch.fsck.badTimezone=ignore https://github.com/psf/requests.git +``` + +You can also apply this setting to your global Git config: + +```shell +git config --global fetch.fsck.badTimezone ignore +``` + +--- + +[![Kenneth Reitz](https://raw.githubusercontent.com/psf/requests/main/ext/kr.png)](https://kennethreitz.org) [![Python Software Foundation](https://raw.githubusercontent.com/psf/requests/main/ext/psf.png)](https://www.python.org/psf) + + diff --git a/lib/requests-2.28.2.dist-info/RECORD b/lib/requests-2.28.2.dist-info/RECORD new file mode 100644 index 0000000..76afaf0 --- /dev/null +++ b/lib/requests-2.28.2.dist-info/RECORD @@ -0,0 +1,42 @@ +requests-2.28.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +requests-2.28.2.dist-info/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142 +requests-2.28.2.dist-info/METADATA,sha256=7164jsFZN3Llh_nX_oBRqetlTdcULRBjjnQlzrtn6Y0,4619 +requests-2.28.2.dist-info/RECORD,, +requests-2.28.2.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 +requests-2.28.2.dist-info/top_level.txt,sha256=fMSVmHfb5rbGOo6xv-O_tUX6j-WyixssE-SnwcDRxNQ,9 +requests/__init__.py,sha256=xX0rLGvoljtzG8oDF-ZYuTlgL_BQgKoxUZ1e3cpNHX8,4972 +requests/__pycache__/__init__.cpython-39.pyc,, +requests/__pycache__/__version__.cpython-39.pyc,, +requests/__pycache__/_internal_utils.cpython-39.pyc,, +requests/__pycache__/adapters.cpython-39.pyc,, +requests/__pycache__/api.cpython-39.pyc,, +requests/__pycache__/auth.cpython-39.pyc,, +requests/__pycache__/certs.cpython-39.pyc,, +requests/__pycache__/compat.cpython-39.pyc,, +requests/__pycache__/cookies.cpython-39.pyc,, +requests/__pycache__/exceptions.cpython-39.pyc,, +requests/__pycache__/help.cpython-39.pyc,, +requests/__pycache__/hooks.cpython-39.pyc,, +requests/__pycache__/models.cpython-39.pyc,, +requests/__pycache__/packages.cpython-39.pyc,, +requests/__pycache__/sessions.cpython-39.pyc,, +requests/__pycache__/status_codes.cpython-39.pyc,, +requests/__pycache__/structures.cpython-39.pyc,, +requests/__pycache__/utils.cpython-39.pyc,, +requests/__version__.py,sha256=h48zn-oFukaXrYHocdadp_hIszWyd_PGrS8Eiii6aoc,435 +requests/_internal_utils.py,sha256=aSPlF4uDhtfKxEayZJJ7KkAxtormeTfpwKSBSwtmAUw,1397 +requests/adapters.py,sha256=sEnHGl4mJz4QHBT8jG6bU5aPinUtdoH3BIuAIzT-X74,21287 +requests/api.py,sha256=dyvkDd5itC9z2g0wHl_YfD1yf6YwpGWLO7__8e21nks,6377 +requests/auth.py,sha256=h-HLlVx9j8rKV5hfSAycP2ApOSglTz77R0tz7qCbbEE,10187 +requests/certs.py,sha256=Z9Sb410Anv6jUFTyss0jFFhU6xst8ctELqfy8Ev23gw,429 +requests/compat.py,sha256=yxntVOSEHGMrn7FNr_32EEam1ZNAdPRdSE13_yaHzTk,1451 +requests/cookies.py,sha256=kD3kNEcCj-mxbtf5fJsSaT86eGoEYpD3X0CSgpzl7BM,18560 +requests/exceptions.py,sha256=DhveFBclVjTRxhRduVpO-GbMYMID2gmjdLfNEqNpI_U,3811 +requests/help.py,sha256=gPX5d_H7Xd88aDABejhqGgl9B1VFRTt5BmiYvL3PzIQ,3875 +requests/hooks.py,sha256=CiuysiHA39V5UfcCBXFIx83IrDpuwfN9RcTUgv28ftQ,733 +requests/models.py,sha256=-DlKi0or8gFAM6VzutobXvvBW_2wrJuOF5NfndTIddA,35223 +requests/packages.py,sha256=DXgv-FJIczZITmv0vEBAhWj4W-5CGCIN_ksvgR17Dvs,957 +requests/sessions.py,sha256=KUqJcRRLovNefUs7ScOXSUVCcfSayTFWtbiJ7gOSlTI,30180 +requests/status_codes.py,sha256=FvHmT5uH-_uimtRz5hH9VCbt7VV-Nei2J9upbej6j8g,4235 +requests/structures.py,sha256=-IbmhVz06S-5aPSZuUthZ6-6D9XOjRuTXHOabY041XM,2912 +requests/utils.py,sha256=5_ws-bsKI9EHl7j27yi-6HFzPBKultPgd7HfPrUToWI,33228 diff --git a/lib/requests-2.28.2.dist-info/WHEEL b/lib/requests-2.28.2.dist-info/WHEEL new file mode 100644 index 0000000..57e3d84 --- /dev/null +++ b/lib/requests-2.28.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lib/requests-2.28.2.dist-info/top_level.txt b/lib/requests-2.28.2.dist-info/top_level.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/lib/requests-2.28.2.dist-info/top_level.txt @@ -0,0 +1 @@ +requests diff --git a/lib/requests/__init__.py b/lib/requests/__init__.py new file mode 100644 index 0000000..22db3c1 --- /dev/null +++ b/lib/requests/__init__.py @@ -0,0 +1,180 @@ +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> b'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key1": "value1", + "key2": "value2" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at . + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +import warnings + +import urllib3 + +from .exceptions import RequestsDependencyWarning + +try: + from charset_normalizer import __version__ as charset_normalizer_version +except ImportError: + charset_normalizer_version = None + +try: + from chardet import __version__ as chardet_version +except ImportError: + chardet_version = None + + +def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): + urllib3_version = urllib3_version.split(".") + assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version) == 2: + urllib3_version.append("0") + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1, <= 1.26 + assert major == 1 + assert minor >= 21 + assert minor <= 26 + + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 6.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (6, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 4.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0) + else: + raise Exception("You need either charset_normalizer or chardet installed") + + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + cryptography_version = list(map(int, cryptography_version.split("."))) + except ValueError: + return + + if cryptography_version < [1, 3, 4]: + warning = "Old version of cryptography ({}) may cause slowdown.".format( + cryptography_version + ) + warnings.warn(warning, RequestsDependencyWarning) + + +# Check imported dependencies for compatibility. +try: + check_compatibility( + urllib3.__version__, chardet_version, charset_normalizer_version + ) +except (AssertionError, ValueError): + warnings.warn( + "urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " + "version!".format( + urllib3.__version__, chardet_version, charset_normalizer_version + ), + RequestsDependencyWarning, + ) + +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. +try: + try: + import ssl + except ImportError: + ssl = None + + if not getattr(ssl, "HAS_SNI", False): + from urllib3.contrib import pyopenssl + + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import __version__ as cryptography_version + + _check_cryptography(cryptography_version) +except ImportError: + pass + +# urllib3's DependencyWarnings should be silenced. +from urllib3.exceptions import DependencyWarning + +warnings.simplefilter("ignore", DependencyWarning) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +from . import packages, utils +from .__version__ import ( + __author__, + __author_email__, + __build__, + __cake__, + __copyright__, + __description__, + __license__, + __title__, + __url__, + __version__, +) +from .api import delete, get, head, options, patch, post, put, request +from .exceptions import ( + ConnectionError, + ConnectTimeout, + FileModeWarning, + HTTPError, + JSONDecodeError, + ReadTimeout, + RequestException, + Timeout, + TooManyRedirects, + URLRequired, +) +from .models import PreparedRequest, Request, Response +from .sessions import Session, session +from .status_codes import codes + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter("default", FileModeWarning, append=True) diff --git a/lib/requests/__pycache__/__init__.cpython-39.pyc b/lib/requests/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50216eb6e0f9e5dcf3cf33db1f080c8237ac6213 GIT binary patch literal 3854 zcmbVO&2JpH6(7#*mqx2kD~V;hHmxL<){$4T?8J#;%khUCJ8=}lQW}I1IvFkL)p&<9 zQ<58Hl~oT>dn}MYLGG=G{trF(RP;F40tNgpq(Ia6W@a~AxQC*%*hlj5k$j&&(w~@c zE%+Y&X`Y9tEbH$$82xbeBlznlwq;}u^0t;NQ9jgLWFKb#fg!hT*6 zuBaY6*~%uIFd-(N*}XsKs|7ik;_k`xK<!AM|z^jWlj!CA?9NPN>>0QrvEUX}W zGCVEk**wUSlQVou%%et`9%tEEXs5%o@*Fz{JcrMOzmVtId3k|d$i}l_MN&rPMRrjx zum$-hdsDu}-jZ*#x8)^vNiMQQxx|)0mr5poDs0Hh?6Q1^y(5>|vb@5s$R=yb6}BQ* z*{ZzCu4Zy`5VbY7Ca5OXYb1o*avc*t;-MDhnf6q;Z1pq z-I5z@L*8b$Gv4drNAeE4Bk!`i+4zm{p1jZQgPmtjHpPP%F-nJTN{U0H%+^6E#U7?Mp4|jH+(8s}^@>S3M&42scpM?WW{U~1&=1Z38 zfeMTf5e*{xq-XZyXqi5X72WSj5Zx0&)YeV+uCId@{djYScD3IY>#j>F-PqWmflOj$ zs4CPMuob;E2~5W=RP1YbAlgKij79=Ey@9O|1Oj$HJ< zp*IJS94=78=WqxJ>t+ydG+lSS6(>CvwD-+AU1~KTmRE1ky&nBkL=osfpNha7Hr@3w zXo*ORb$Tc9TcGP&b9Fh>=JBI@n_JtPStiOD)aAnXK?s@n5}|hMB>~zX>HMG$?u$f3 zTtuzjm%fT1HDGew^S%*EgX*5wa&TSoRiG`w{~rAH25i;0P5@h8kR#}EyG=TdKK+UH zJu#$j9TVY z5!8+!28Y0cf=4c-44?v_N*D^>D5qtegn>z&P(*3PPcUCn2TC#>+x)gA5=`+%C7tT3 z5U}yuAa&BIVy_|N%&SKZ=gQK59;>tr;Dk-$o7VmxEj15D{J3>*efJ9u3Awx7QbDqN zU+9jBlihY=JiYI8p>_>G`r0m_`K}I(xB^Ad0dUj1gHl`ZydVmU=K+MKr=Vh59l%Qo z#{_!;7}r~egY;WKEaH+$r%qhQCFkrqfuB<%r=h<4 zjAgYD&Z3tS%;ofyS2%t|yRTqE&jPV*8@yX5HSl?f*gusVh2c2-vPi4kjFAjQvsPZs zs9cW6LPv`R%!WLo{s?FnUb>i8AKuyawznQV0KgkddNHK6)*%dLUV+5460{*L1w!K% z!23f#;-OFtJFN{eEiHEqz-+@!UC;Boa9t?ROQ(l@ApwPfGv;}F-5})9tpT|D9Ra-w z=nZfmhEtx$g~n9J(gFtHaRK-+fE`R=LR?q%Jn)X^DXjHIEv;}73L{bn9u8?~A8wbl z8fTkmbs9v{5?-^ZlXOj5ElLA4p1K#q?b5=-@~BT1jGaJ=7-Xj&1flp0o)^Uxk&fZx zDVLr4Wc%~2`vM;yIb-}OoI6-KU0gFmph&C3shv3f%#V6c1rHRQuG8@^o<2r-81jab zPCZc~fg6_>d!*Gtr}k9nB!>GBB2e8H8gHt!s`Cy6sf^m_w93UZzZ;sJ22s=Kp@--( zxNX5>6x~rRaw~Pk0LXNf+HCgK#LAqk5Si2QN7>^ox3^lroSK8F3>mV-O(*YYz^tKD z)p?x0h~xt9;XtybRS1-CjA8@u%pPJ2UYM6q=2Q?p2hRX>nHVFD@XG3=HK!Ipj?ETx z%@H#eaf7;sXJ#m#1)&itt%h;C4L7`M;GCr{L!;h7vW(;kk|vTBB&$fSB3VOn4as#N zsRQpE^)3$ILvjPj`$#aw)CWiqP1T1;ZX&sbWCO`JjojL0*02<*U`F$QlEWJ_a+vT&SbYCqMSX~3zHUHg|^vj6FhBNuNA g*PbMGyXMpZ$0kVyTc-w`X|h^hg8v-uXwN$T2K1^e9RL6T literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/__version__.cpython-39.pyc b/lib/requests/__pycache__/__version__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66ffdc3485f5b37ee50fa4bfec4d01ea912cd4d4 GIT binary patch literal 527 zcmZ8e%}N|W5bj;~XLihDhzIfH93v#NGAEHBiXIe$_ z@R}S1!ME9Cd;!lnrAFPLR@22-Uv+hVRW18{pONj~F2j9;v4=W%ZN#xH+V%@MV4OjN zH=xOzFvsVh#aqzkZRqe0ba@v%?m>_Dz&~ZH^Ha~@Wr^0=YUya=Qk@5TXJ<#j4{d_I zax8NjRx7DQ8$@IOQ@FV!9v% zpiPV_oYFZFT_H-75ttX^CmQ?AQpH87G9ca;LYCB`A^h<_#<5f{RhddKxAoF|7zopbj_ts7yUljJdXCkswTq-p zu*c;9{UGU)1DyC4e1pAm>Q~^v@Fd%%4al=^o}{Pu^nSme9Jg9N!EyZSEnd_J`O{8b z9k@n5!%sg!!%09mQDH=?{U2W& zzJFh3mx6<4c3YSk8y7M$B9oe#No-iGfF(F9roh=cS2|;(8SAfV_*WB=Oc;k8Bxjjq z@Pkn>1&kWQm7Tc@unD9&D9zs21%}5O^G@^FwEY)|4>@^3(Y9&1F)BoAgp9ONdKcHu z9-ehW{m5Ds%10mU&3FH^q_=HgICyxridmfP&9`qXl(kqVLL?r>u15tO`yJw^-$P@_ zf*AUM1A zS(B)@z9v7>IZ}D;aU_wF?}&XPRbnc$eM;=_0pXPaxkGpr>8;IwKLXV2R7hZAjMxc8 z`z0NjL{Z~&Ffn4bf;XAODo*f>?)VPWZjK96%OEROJ#=p-G8s!IkX&@wy+Ru{f^~q) zV5MQHmYHGqXJ&$e*=LV2;o~K3U1fFAWuq)h-2qj_riWjgw5!Vg>y#CYE$fapDBnC? zHqo;xf}T>D!e`c8-_5bPG#mEp&Z_4~=*Yd7JzUimbJEzN9z_!9CRO%jwy7)J-YOeW z#Iq!dN>4#qUq0-moEml_QV8COzCE~md=!1vJ2>j~qmzSsy{O-dK7TOm1-JfxZ!qi! z@4mi;IZppQcO&P^O*~lEMl;l(YT-SD-BnR{gM;BW{ecsaYvPsut*(^=q4~))<3$Re bT50LG&{P{uZ`1R!ef(9LwE1_n?zjE{imbX( literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/adapters.cpython-39.pyc b/lib/requests/__pycache__/adapters.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1840aa7d95ee2cb7b0ac1f528fd78f72dd55bb5d GIT binary patch literal 16863 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 JkDsK=}n{60qq84Xqo}(HjFSJ$kK7lgcMbh zPU~C@dx*WnFzg}r8ob?AUSU@|l-)HIp^xABbk32)!^5=G2_3u+|Juy|tUJy>*0_EJ zXncdW`pI>i+#$|ol)JgdT;l%h6kEOn-t4LC*iq><;{M_k-rn5ui4Xo+oiw14yPHm4 zJNHO)!F}XGUMH=~r7AKc?Yu!+|7ZQ}TGlpc&$B{H{*K;F7V?Nhu2a~rX|nd*LLRUV z?~-oyH7(ZTOJs>JlV!d_Rw^xIH~1>KS@AYo; z8SIc9KpcBy0K2e<^B1r?cFBJ3ojCp8<=>%%w^LAB4Kl&YRENKQdWGSe2~{x{`IJFi zO1Pk6s@N=^q(#mkk0~#O)bTu;jwqvg7E`4#X*dG?4xosCnCXcq;{6z7J{Sjs_)y0Z zcuETukA;j!X?FfDmAMK(O*SnwWwD+>T)=xB^YjdzFF-0Pia~f~H_huS5Amvo@ZFof zYkJ?G9KU`EnaJT=DTPF-eJpd3kiVpv?$=E4B4sQcF)+=ev@B_HYPv^LZIy+MF|AC9 za|&mvP0%IIl5)1cm=8#IlBz`ER$*|aL_xxj-1t_JK9T~h|>D3c9TWz&K^AdGMSRJJT#6pPW3iL8nn+7?eqd(Ie-3fZTkbS?BOELm8#uwvncg;fhT5lnXxzvvseW}mEE*s!o^VG98lM@!QlO)1l~NUTvG z%{1079Hk07&Tb)7qgb^1n2A)IPBG;mX_n|&iEbTy1g9Y9<{ysy(x$fX@2eMI4S!VF z0mG9_(sK9`)VUVr@U+y4nxweThxj7BKYT?;Lq#?0l-OJ8DX8IxcHf=%*r1%5;Q73} vzs7^sQO^$!m!RGN=EYVK;~6V>;k?Z9{K2ypxd0&~SO literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/cookies.cpython-39.pyc b/lib/requests/__pycache__/cookies.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fc0db7146e1b7734ff73e7f8046712c40a7d1e5 GIT binary patch literal 18795 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 T{G|1hR;7Hne71al<@kRA{fOoN literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/exceptions.cpython-39.pyc b/lib/requests/__pycache__/exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3a19c83b1f8cda5d1e1f5439c590a47a24a95af GIT binary patch literal 6096 zcmbVQTXPf16`qk~Nfy2#Y-8??xtayR!Y;{@n~i}iTVRN7a#1-YkEh$#7->dy_h6** z?n9tz^N`x#kWJ-LqJ#xU~d;)mF;0o|0p8}o|6^w8I z_#m$WR}CHmKE$Vir%nHH;2Ay(JS!?<95YNna+uFSG6$TmPXZs|M}d!;H>QA(@#Db9 z4L%5bf}aFFDaxXX5e`9eiZe)9BAJHdG@plLK9S5oa)zIUwy1?FyRIB_*_OoC|qhGGCKfWtv zAb*7IkYw_!wMQ#=gd1>SyH&%Y|y3*TA=t3HCY z6r!nw!QK8sdh6>=AqCrW6mxv$w70ySFbLHfg5d9fyz7Qi3eNVL9v0VheBSc>9o7v( zSz-7kI;t7q694|MfE&6daj8F|+0Xsmc*Xa*uk-u)eXC&`NuKAv&jIE3aTnV1_P({7 zQ@1tx@chf1!S=4{p$jjq-I0CU!&v{czq8?4Ia*)u_I=;ep40MrqOa`1QrVuTn`|>7 zx{2j-LaKx96q_vA+7&MLI4bvLD^V%wyikntouDI&U@uE(qEgdQj@B|7QDH~OsN86H zn7Yw`k_$?-wxfb0cT`k-x<~)bo3F9OjfUHDlxj3yTmOB$d~4&05>jofxzg)w+!5-j z4muk<9okXb(=zywqUcP`Dp6C{~Y_t$rFqZ^k$bB z0Vc>(28LN%d0mi)Fov9_W`>$sYTj@&cZS;>9vs7^a%jr7B>WyTdbWs5(bKn?!LO`( zffrzNBa+tFn_mvX7WXe`#*Moguh-&*W}dGDwyOgFEvF#Ir57LJQrFOEYuDblcI1(mq@JLJjqhVZe!w{GXdQhcO(B>h81fWy4uYAPTT+qr5)cDY}=Dc$DZJ{ zT7$RAMr#bAz-OTn%+nVT4j^OTjzE?g`Tirb3rqzjFbqbZC`YSgsv}%)+jHah9VtjD z7?d-Fn8DlWPJ=PXBWav?XWK$47(3gRLEG$jDiAWn*$(_v$mfE`F^80}0`Hqy4%!_@ zzoP+Twq~E@IJCWxmt>pfUg8)}n~AXy@}zlO#(J$%)$Yx`GvdI3R$v1kGCUgMgVsm; zeyTq8yG+R%Q|^9348MqFL4&e zUU__pOfV5;U_);BX^IWu%xo%;W$>aK6FIyD%K*y2;W*~}JjH=H1&A2g}dr-(}p<4vq%1M4SeJ))b}N zLFk)ol}x8LWP=#kJxsABoSp5H)d!?X56NZk(h@U=J%f-!JXs$-APu&ir-C_syB@~t zGf8w=jBXgwvmo~86j8#NiB=M#&|U9bpXGoa6+bFyi)OIb zCpe7JIWTZij0k6DG@dZ}5|L5JUX4gK%{{q5C@7-D%FD>^JlKd7JHnaSO(yIf(@9E7 zni-FU5&)W=_+~&Cq`;_+20MFnl6DY=1+<`Tr8{uxzJT6aGI~<2!)xwMkwFtmp4JD5FhWOX`Jw(P_RL zp#Lyp3n20=MT~G}Vu#kW6iyrGqY9BC#M-<;+H_;aY#UB$jQu!Blt+bDC$N%aZl?WTAT&dg)xi7&;T z=W}PPC2j{iq4bMEB0$!LqZ6M~=y>htR#a2HNi5J3|w!tKj;#6nDZa z3unxCje0edt(LcSBbAw(QZ5>&Jsx~YjEb(QhfR$sk79(VlvLs7gG^i#Q`IG@?2%M@ zNJ_OOC7+T}LkX0m{3WXVp3WxYi}DOf=`@yf07^P%Bn5U!aZ*xDibmJFouoIpz9hes zWXVw}(Vrw+imK2nk}QYH$^HY{C`l{+Pz@cGe;I$L`n8Q7>L!{(aV$5MpPHO1R41ki W$=_(T6yH?~@gE_&!+$)f7XAkguV6C( literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/help.cpython-39.pyc b/lib/requests/__pycache__/help.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f5cd02a20488db1af10c6c553efa4a15d5c0d41 GIT binary patch literal 2810 zcmZ`*OOM+|5+<9?hbYd&<7D#KJcu|OMS(3l_F}Q{E`r56lfXtiGw|41gb+Yb>Yfob z6v;GOb~Mod0Vdd6_ORE@!9M1YYkoxjLHe4LFF6Ez3b1ynnv^_t5K*k|s;;iCuCDsn z$;ygv!0-CEFR^oLhVdgZr+*$We}}jF83;2XgPDwEW@PG?L`1h%Wa*YhRJV3y>(+@J zW-*$%6EA9+#!;Z9e&qkt$Tz9M+p~db;KNAde`4h1(n2SQf~C&CmO4SBvkrFbY-O?< zt(pety{2t_x71q&du#BngZ{>)+UNBMXOV0TMjYmTX+&q3$6C;CFrT%dJF!OKF(aKy8ci?^$8?;!-p*nVgN?V73` z$9K7?(jt#zU|Z#^D0yC0*$~whKLQ;a-fQqy{{|wB`{o1V*qDRQ;K|Ft6X4B!3=#s8 zKC|vyl8)`Mqp7Q@_rN+fb+5(fp(Va#_I-ceNDjyY6D+(9796mUFKupu#0AM0O9^G( zu{k&9CTo37VE2CV1MKG(ms634NyyTn4DY3K6v`0~cV}``j0LD5XilL^;)Xr<8&i~Zj>s?7|iLcl87EELlmkD(WSP>7I!mY@UY zLsjb>c|ZXh!31kHA+gp z2)o%}E_ z?&V#gD27wHO$57b&_&zE|8@zeRCRvU3MMLFnCB9&FmDs`rum9u5xr1^fK>`5{% z1T<-0H$zdVK=6zw6_C3;Q*>OU=*;RfOKm>QFYJC}h(AE7{c_{l)&89dinPBs6lvLi zpI3*nDEkMcj3J1Oi@t;-xZM9J9rUYI@(X1$JWLLF)ffEJDGXH?vHW}GOsze$%{Q>1 z-vVN^J>r?Z>6z4Y@cBQr1L*sY9jli6gc9*8Xg-Auh{`MQYGn8YI!Bu|Hq^Ds%&*BY zBHt_2kk;(-0{Ix=F^<$YDd`Dd2eTxgMQR=s#A0Zzrlryq0~mGns3kp70_SOtGRkb` zfGu0L#D8Q9@TN8P$8F~Rm)3I74de=7sC{CARX}>+SC_mi*T(H*k6Ck%(WxoEhZg4M zgxR3oLX?@ek^Nb-qtn+y?ch@HSlZVUXDb%|!wS_8+6q)_H(!ou-Dtm2H9fGEE<76WWYYtsra zCtl)s^kOX-juKIE8AEnXk}N&qA_B1A0kGfO`$)M{k!9)NigHnpaj7)K( z+t7(W-nzASeP=t~zH{>*Tep;ZzyY+S5SXZ7N74HF&F+fk;D|VIYkbryIcIpK8~Er+CJwVR`4gzk0T3{t8~+2?hWF literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/hooks.cpython-39.pyc b/lib/requests/__pycache__/hooks.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2186be9638ad29074496840ab73348a30153b475 GIT binary patch literal 967 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 literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/packages.cpython-39.pyc b/lib/requests/__pycache__/packages.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c6104663ab93167dc56253e441e805f485be0e4 GIT binary patch literal 688 zcmY+BL2uJA6o8+dHp!YcOgjMM)N2nERBlr>Au*69O%u{Uh$txX;z#Q?O;X#{)Ly&m ze!(9oL(!>_yin*;Ep+T0($-Mq9@ z{j!P!CmBSrYS$|gkq?j$8h~{9hCn5?--(>sHX?$~q{%6Fc!M`D90DI9r_X`A2k_}_ ze@`gI%aB_c6GHD>{Vv zcz%Rr(uu-*l>M3#TFUpD68@vyVtD-kL%wxGZN~pZ8&wl;IXQt|>!^g|bJCJ1h7i!yYx>LS~sbePJ4c=gg4)YDeOH zfl8}>$Y+c;{eWcBVQIGmM*)UTy1r18&~%x!5U?LUDDKZ{JG+&GQ* zmt|T>BI>jxF>Y3*EK{bD38js9cPC{WyD%-Knbhh`=&|vWBxf_MGOTliHdbPpu@t|6 zd}Pbj>i6-hz4*OCsp6AViZVVxHPuBKk4l}WG2ZTlL;N8&VALgUj-gYl;2XB$ty mTpE9PccXn`OQ^0@KBc5>#iu@f*z`&0gyb=KLS0hXHu(d$;>Jz@ literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/sessions.cpython-39.pyc b/lib/requests/__pycache__/sessions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f8f6bb30b2f661e5f797a018076379375cdf9f7 GIT binary patch literal 19608 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 A3IG5A literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/status_codes.cpython-39.pyc b/lib/requests/__pycache__/status_codes.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ed073fd96be4b0a1bc3b21a25629743aa3bd26e GIT binary patch literal 4212 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 N<+(-YM(M_v{{hM#D*gZf literal 0 HcmV?d00001 diff --git a/lib/requests/__pycache__/utils.cpython-39.pyc b/lib/requests/__pycache__/utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc3bd866dbb59c840e6af14b67f22e75d818354a GIT binary patch literal 24196 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 being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session ` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + + __attrs__ = [ + "max_retries", + "config", + "_pool_connections", + "_pool_maxsize", + "_pool_block", + ] + + def __init__( + self, + pool_connections=DEFAULT_POOLSIZE, + pool_maxsize=DEFAULT_POOLSIZE, + max_retries=DEFAULT_RETRIES, + pool_block=DEFAULT_POOLBLOCK, + ): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super().__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager( + self._pool_connections, self._pool_maxsize, block=self._pool_block + ) + + def init_poolmanager( + self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs + ): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager( + num_pools=connections, + maxsize=maxsize, + block=block, + strict=True, + **pool_kwargs, + ) + + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith("socks"): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith("https") and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) + + if not cert_loc or not os.path.exists(cert_loc): + raise OSError( + f"Could not find a suitable TLS CA certificate bundle, " + f"invalid path: {cert_loc}" + ) + + conn.cert_reqs = "CERT_REQUIRED" + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = "CERT_NONE" + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + conn.key_file = None + if conn.cert_file and not os.path.exists(conn.cert_file): + raise OSError( + f"Could not find the TLS certificate file, " + f"invalid path: {conn.cert_file}" + ) + if conn.key_file and not os.path.exists(conn.key_file): + raise OSError( + f"Could not find the TLS key file, invalid path: {conn.key_file}" + ) + + def build_response(self, req, resp): + """Builds a :class:`Response ` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter ` + + :param req: The :class:`PreparedRequest ` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, "status", None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, "headers", {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode("utf-8") + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, "http") + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL( + "Please check proxy URL. It is malformed " + "and could be missing the host." + ) + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + is_proxied_http_request = proxy and scheme != "https" + using_socks_proxy = False + if proxy: + proxy_scheme = urlparse(proxy).scheme.lower() + using_socks_proxy = proxy_scheme.startswith("socks") + + url = request.path_url + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter `. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return headers + + def send( + self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None + ): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection(request.url, proxies) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers( + request, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + ) + + chunked = not (request.body is None or "Content-Length" in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError: + raise ValueError( + f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, " + f"or a single float to set both timeouts to the same value." + ) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + if not chunked: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout, + ) + + # Send the request. + else: + if hasattr(conn, "proxy_pool"): + conn = conn.proxy_pool + + low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) + + try: + skip_host = "Host" in request.headers + low_conn.putrequest( + request.method, + url, + skip_accept_encoding=True, + skip_host=skip_host, + ) + + for header, value in request.headers.items(): + low_conn.putheader(header, value) + + low_conn.endheaders() + + for i in request.body: + low_conn.send(hex(len(i))[2:].encode("utf-8")) + low_conn.send(b"\r\n") + low_conn.send(i) + low_conn.send(b"\r\n") + low_conn.send(b"0\r\n\r\n") + + # Receive the response from the server + r = low_conn.getresponse() + + resp = HTTPResponse.from_httplib( + r, + pool=conn, + connection=low_conn, + preload_content=False, + decode_content=False, + ) + except Exception: + # If we hit any problems here, clean up the connection. + # Then, raise so that we can handle the actual exception. + low_conn.close() + raise + + except (ProtocolError, OSError) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + elif isinstance(e, _InvalidHeader): + raise InvalidHeader(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/lib/requests/api.py b/lib/requests/api.py new file mode 100644 index 0000000..2f71aae --- /dev/null +++ b/lib/requests/api.py @@ -0,0 +1,157 @@ +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request `. + + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response ` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req + + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("get", url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("options", url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). + :return: :class:`Response ` object + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return request("head", url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("post", url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("put", url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("patch", url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("delete", url, **kwargs) diff --git a/lib/requests/auth.py b/lib/requests/auth.py new file mode 100644 index 0000000..9733686 --- /dev/null +++ b/lib/requests/auth.py @@ -0,0 +1,315 @@ +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import hashlib +import os +import re +import threading +import time +import warnings +from base64 import b64encode + +from ._internal_utils import to_native_string +from .compat import basestring, str, urlparse +from .cookies import extract_cookies_to_jar +from .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded" +CONTENT_TYPE_MULTI_PART = "multipart/form-data" + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(username), + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(type(password)), + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode("latin1") + + if isinstance(password, str): + password = password.encode("latin1") + + authstr = "Basic " + to_native_string( + b64encode(b":".join((username, password))).strip() + ) + + return authstr + + +class AuthBase: + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError("Auth hooks must be callable.") + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other): + return not self == other + + def __call__(self, r): + r.headers["Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + + def __call__(self, r): + r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, "init"): + self._thread_local.init = True + self._thread_local.last_nonce = "" + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :rtype: str + """ + + realm = self._thread_local.chal["realm"] + nonce = self._thread_local.chal["nonce"] + qop = self._thread_local.chal.get("qop") + algorithm = self._thread_local.chal.get("algorithm") + opaque = self._thread_local.chal.get("opaque") + hash_utf8 = None + + if algorithm is None: + _algorithm = "MD5" + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == "MD5" or _algorithm == "MD5-SESS": + + def md5_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.md5(x).hexdigest() + + hash_utf8 = md5_utf8 + elif _algorithm == "SHA": + + def sha_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha1(x).hexdigest() + + hash_utf8 = sha_utf8 + elif _algorithm == "SHA-256": + + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha256(x).hexdigest() + + hash_utf8 = sha256_utf8 + elif _algorithm == "SHA-512": + + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha512(x).hexdigest() + + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731 + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += f"?{p_parsed.query}" + + A1 = f"{self.username}:{realm}:{self.password}" + A2 = f"{method}:{path}" + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = f"{self._thread_local.nonce_count:08x}" + s = str(self._thread_local.nonce_count).encode("utf-8") + s += nonce.encode("utf-8") + s += time.ctime().encode("utf-8") + s += os.urandom(8) + + cnonce = hashlib.sha1(s).hexdigest()[:16] + if _algorithm == "MD5-SESS": + HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}") + + if not qop: + respdig = KD(HA1, f"{nonce}:{HA2}") + elif qop == "auth" or "auth" in qop.split(","): + noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}" + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = ( + f'username="{self.username}", realm="{realm}", nonce="{nonce}", ' + f'uri="{path}", response="{respdig}"' + ) + if opaque: + base += f', opaque="{opaque}"' + if algorithm: + base += f', algorithm="{algorithm}"' + if entdig: + base += f', digest="{entdig}"' + if qop: + base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"' + + return f"Digest {base}" + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/psf/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + r.request.body.seek(self._thread_local.pos) + s_auth = r.headers.get("www-authenticate", "") + + if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2: + + self._thread_local.num_401_calls += 1 + pat = re.compile(r"digest ", flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + extract_cookies_to_jar(prep._cookies, r.request, r.raw) + prep.prepare_cookies(prep._cookies) + + prep.headers["Authorization"] = self.build_digest_header( + prep.method, prep.url + ) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + r.headers["Authorization"] = self.build_digest_header(r.method, r.url) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook("response", self.handle_401) + r.register_hook("response", self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r + + def __eq__(self, other): + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other): + return not self == other diff --git a/lib/requests/certs.py b/lib/requests/certs.py new file mode 100644 index 0000000..be422c3 --- /dev/null +++ b/lib/requests/certs.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" +from certifi import where + +if __name__ == "__main__": + print(where()) diff --git a/lib/requests/compat.py b/lib/requests/compat.py new file mode 100644 index 0000000..6776163 --- /dev/null +++ b/lib/requests/compat.py @@ -0,0 +1,79 @@ +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module previously handled import compatibility issues +between Python 2 and Python 3. It remains for backwards +compatibility until the next major version. +""" + +try: + import chardet +except ImportError: + import charset_normalizer as chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = _ver[0] == 2 + +#: Python 3.x? +is_py3 = _ver[0] == 3 + +# json/simplejson module import resolution +has_simplejson = False +try: + import simplejson as json + + has_simplejson = True +except ImportError: + import json + +if has_simplejson: + from simplejson import JSONDecodeError +else: + from json import JSONDecodeError + +# Keep OrderedDict for backwards compatibility. +from collections import OrderedDict +from collections.abc import Callable, Mapping, MutableMapping +from http import cookiejar as cookielib +from http.cookies import Morsel +from io import StringIO + +# -------------- +# Legacy Imports +# -------------- +from urllib.parse import ( + quote, + quote_plus, + unquote, + unquote_plus, + urldefrag, + urlencode, + urljoin, + urlparse, + urlsplit, + urlunparse, +) +from urllib.request import ( + getproxies, + getproxies_environment, + parse_http_list, + proxy_bypass, + proxy_bypass_environment, +) + +builtin_str = str +str = str +bytes = bytes +basestring = (str, bytes) +numeric_types = (int, float) +integer_types = (int,) diff --git a/lib/requests/cookies.py b/lib/requests/cookies.py new file mode 100644 index 0000000..bf54ab2 --- /dev/null +++ b/lib/requests/cookies.py @@ -0,0 +1,561 @@ +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import calendar +import copy +import time + +from ._internal_utils import to_native_string +from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest: + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + def get_full_url(self): + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get("Host"): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = to_native_string(self._r.headers["Host"], encoding="utf-8") + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse( + [ + parsed.scheme, + host, + parsed.path, + parsed.params, + parsed.query, + parsed.fragment, + ] + ) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + def add_header(self, key, val): + """cookielib has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError( + "Cookie headers should be added with add_unredirected_header()" + ) + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse: + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, "_original_response") and response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get("Cookie") + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name( + self, name, domain=kwargs.get("domain"), path=kwargs.get("path") + ) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + if (domain is None or cookie.domain == domain) and ( + path is None or cookie.path == path + ): + dictionary[cookie.name] = cookie.value + return dictionary + + def __contains__(self, name): + try: + return super().__contains__(name) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``. + """ + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie, *args, **kwargs): + if ( + hasattr(cookie.value, "startswith") + and cookie.value.startswith('"') + and cookie.value.endswith('"') + ): + cookie.value = cookie.value.replace('\\"', "") + return super().set_cookie(cookie, *args, **kwargs) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super().update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: + # if there are multiple cookies that meet passed in criteria + raise CookieConflictError( + f"There are multiple cookies with name, {name!r}" + ) + # we will eventually return this as long as no cookie conflict + toReturn = cookie.value + + if toReturn: + return toReturn + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop("_cookies_lock") + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if "_cookies_lock" not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, "copy"): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result = { + "version": 0, + "name": name, + "value": value, + "port": None, + "domain": "", + "path": "/", + "secure": False, + "expires": None, + "discard": True, + "comment": None, + "comment_url": None, + "rest": {"HttpOnly": None}, + "rfc2109": False, + } + + badargs = set(kwargs) - set(result) + if badargs: + raise TypeError( + f"create_cookie() got unexpected keyword arguments: {list(badargs)}" + ) + + result.update(kwargs) + result["port_specified"] = bool(result["port"]) + result["domain_specified"] = bool(result["domain"]) + result["domain_initial_dot"] = result["domain"].startswith(".") + result["path_specified"] = bool(result["path"]) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel["max-age"]: + try: + expires = int(time.time() + int(morsel["max-age"])) + except ValueError: + raise TypeError(f"max-age: {morsel['max-age']} must be integer") + elif morsel["expires"]: + time_template = "%a, %d-%b-%Y %H:%M:%S GMT" + expires = calendar.timegm(time.strptime(morsel["expires"], time_template)) + return create_cookie( + comment=morsel["comment"], + comment_url=bool(morsel["comment"]), + discard=False, + domain=morsel["domain"], + expires=expires, + name=morsel.key, + path=morsel["path"], + port=None, + rest={"HttpOnly": morsel["httponly"]}, + rfc2109=False, + secure=bool(morsel["secure"]), + value=morsel.value, + version=morsel["version"] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError("You can only merge into CookieJar") + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/lib/requests/exceptions.py b/lib/requests/exceptions.py new file mode 100644 index 0000000..e1cedf8 --- /dev/null +++ b/lib/requests/exceptions.py @@ -0,0 +1,141 @@ +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" +from urllib3.exceptions import HTTPError as BaseHTTPError + +from .compat import JSONDecodeError as CompatJSONDecodeError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + response = kwargs.pop("response", None) + self.response = response + self.request = kwargs.pop("request", None) + if response is not None and not self.request and hasattr(response, "request"): + self.request = self.response.request + super().__init__(*args, **kwargs) + + +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + +class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError): + """Couldn't decode the text into json""" + + def __init__(self, *args, **kwargs): + """ + Construct the JSONDecodeError instance first with all + args. Then use it's args to construct the IOError so that + the json specific args aren't used as IOError specific args + and the error message from JSONDecodeError is preserved. + """ + CompatJSONDecodeError.__init__(self, *args) + InvalidJSONError.__init__(self, *self.args, **kwargs) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL scheme (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """The URL scheme provided is either invalid or unsupported.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content.""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed.""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body.""" + + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" diff --git a/lib/requests/help.py b/lib/requests/help.py new file mode 100644 index 0000000..8fbcd65 --- /dev/null +++ b/lib/requests/help.py @@ -0,0 +1,134 @@ +"""Module containing bug report helper(s).""" + +import json +import platform +import ssl +import sys + +import idna +import urllib3 + +from . import __version__ as requests_version + +try: + import charset_normalizer +except ImportError: + charset_normalizer = None + +try: + import chardet +except ImportError: + chardet = None + +try: + from urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import cryptography + import OpenSSL + + +def _implementation(): + """Return a dict with the Python implementation and version. + + Provide both the name and the version of the Python implementation + currently running. For example, on CPython 3.10.3 it will return + {'name': 'CPython', 'version': '3.10.3'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + if implementation == "CPython": + implementation_version = platform.python_version() + elif implementation == "PyPy": + implementation_version = "{}.{}.{}".format( + sys.pypy_version_info.major, + sys.pypy_version_info.minor, + sys.pypy_version_info.micro, + ) + if sys.pypy_version_info.releaselevel != "final": + implementation_version = "".join( + [implementation_version, sys.pypy_version_info.releaselevel] + ) + elif implementation == "Jython": + implementation_version = platform.python_version() # Complete Guess + elif implementation == "IronPython": + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = "Unknown" + + return {"name": implementation, "version": implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + "system": platform.system(), + "release": platform.release(), + } + except OSError: + platform_info = { + "system": "Unknown", + "release": "Unknown", + } + + implementation_info = _implementation() + urllib3_info = {"version": urllib3.__version__} + charset_normalizer_info = {"version": None} + chardet_info = {"version": None} + if charset_normalizer: + charset_normalizer_info = {"version": charset_normalizer.__version__} + if chardet: + chardet_info = {"version": chardet.__version__} + + pyopenssl_info = { + "version": None, + "openssl_version": "", + } + if OpenSSL: + pyopenssl_info = { + "version": OpenSSL.__version__, + "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}", + } + cryptography_info = { + "version": getattr(cryptography, "__version__", ""), + } + idna_info = { + "version": getattr(idna, "__version__", ""), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""} + + return { + "platform": platform_info, + "implementation": implementation_info, + "system_ssl": system_ssl_info, + "using_pyopenssl": pyopenssl is not None, + "using_charset_normalizer": chardet is None, + "pyOpenSSL": pyopenssl_info, + "urllib3": urllib3_info, + "chardet": chardet_info, + "charset_normalizer": charset_normalizer_info, + "cryptography": cryptography_info, + "idna": idna_info, + "requests": { + "version": requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/lib/requests/hooks.py b/lib/requests/hooks.py new file mode 100644 index 0000000..d181ba2 --- /dev/null +++ b/lib/requests/hooks.py @@ -0,0 +1,33 @@ +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" +HOOKS = ["response"] + + +def default_hooks(): + return {event: [] for event in HOOKS} + + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, "__call__"): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/lib/requests/models.py b/lib/requests/models.py new file mode 100644 index 0000000..617a413 --- /dev/null +++ b/lib/requests/models.py @@ -0,0 +1,1034 @@ +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +import datetime + +# Import encoding now, to avoid implicit import later. +# Implicit import within threads may cause LookupError when standard library is in a ZIP, +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. +import encodings.idna # noqa: F401 +from io import UnsupportedOperation + +from urllib3.exceptions import ( + DecodeError, + LocationParseError, + ProtocolError, + ReadTimeoutError, + SSLError, +) +from urllib3.fields import RequestField +from urllib3.filepost import encode_multipart_formdata +from urllib3.util import parse_url + +from ._internal_utils import to_native_string, unicode_is_ascii +from .auth import HTTPBasicAuth +from .compat import ( + Callable, + JSONDecodeError, + Mapping, + basestring, + builtin_str, + chardet, + cookielib, +) +from .compat import json as complexjson +from .compat import urlencode, urlsplit, urlunparse +from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header +from .exceptions import ( + ChunkedEncodingError, + ConnectionError, + ContentDecodingError, + HTTPError, + InvalidJSONError, + InvalidURL, +) +from .exceptions import JSONDecodeError as RequestsJSONDecodeError +from .exceptions import MissingSchema +from .exceptions import SSLError as RequestsSSLError +from .exceptions import StreamConsumedError +from .hooks import default_hooks +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( + check_header_validity, + get_auth_from_url, + guess_filename, + guess_json_utf, + iter_slices, + parse_header_links, + requote_uri, + stream_decode_response_unicode, + super_len, + to_key_val_list, +) + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin: + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = "/" + + url.append(path) + + query = p.query + if query: + url.append("?") + url.append(query) + + return "".join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, "read"): + return data + elif hasattr(data, "__iter__"): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, "__iter__"): + vs = [vs] + for v in vs: + if v is not None: + result.append( + ( + k.encode("utf-8") if isinstance(k, str) else k, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if not files: + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, "__iter__"): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + ( + field.decode("utf-8") + if isinstance(field, bytes) + else field, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, "read"): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin: + def register_hook(self, event, hook): + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError(f'Unsupported event specified, with event name "{event}"') + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, "__iter__"): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request ` object. + + Used to prepare a :class:`PreparedRequest `, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + + """ + + def __init__( + self, + method=None, + url=None, + headers=None, + files=None, + data=None, + params=None, + auth=None, + cookies=None, + hooks=None, + json=None, + ): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return f"" + + def prepare(self): + """Constructs a :class:`PreparedRequest ` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest ` object, + containing the exact bytes that will be sent to the server. + + Instances are generated from a :class:`Request ` object, and + should not be instantiated manually; doing so may produce undesirable + effects. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + >>> r + + + >>> s = requests.Session() + >>> s.send(r) + + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: integer denoting starting position of a readable file-like body. + self._body_position = None + + def prepare( + self, + method=None, + url=None, + headers=None, + files=None, + data=None, + params=None, + auth=None, + cookies=None, + hooks=None, + json=None, + ): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return f"" + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + import idna + + try: + host = idna.encode(host, uts46=True).decode("utf-8") + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/psf/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode("utf8") + else: + url = str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ":" in url and not url.lower().startswith("http"): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + raise MissingSchema( + f"Invalid URL {url!r}: No scheme supplied. " + f"Perhaps you meant https://{url}?" + ) + + if not host: + raise InvalidURL(f"Invalid URL {url!r}: No host supplied") + + # In general, we want to try IDNA encoding the hostname if the string contains + # non-ASCII characters. This allows users to automatically get the correct IDNA + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + try: + host = self._get_idna_encoded_host(host) + except UnicodeError: + raise InvalidURL("URL has an invalid label.") + elif host.startswith(("*", ".")): + raise InvalidURL("URL has an invalid label.") + + # Carefully reconstruct the network location + netloc = auth or "" + if netloc: + netloc += "@" + netloc += host + if port: + netloc += f":{port}" + + # Bare domains aren't valid URLs. + if not path: + path = "/" + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = f"{query}&{enc_params}" + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + if not data and json is not None: + # urllib3 requires a bytes-like body. Python 2's json.dumps + # provides this natively, but Python 3 gives a Unicode string. + content_type = "application/json" + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + + if not isinstance(body, bytes): + body = body.encode("utf-8") + + is_stream = all( + [ + hasattr(data, "__iter__"), + not isinstance(data, (basestring, list, tuple, Mapping)), + ] + ) + + if is_stream: + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + body = data + + if getattr(body, "tell", None) is not None: + # Record the current file position before reading. + # This will allow us to rewind a file in the event + # of a redirect. + try: + self._body_position = body.tell() + except OSError: + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body + self._body_position = object() + + if files: + raise NotImplementedError( + "Streamed bodies and files are mutually exclusive." + ) + + if length: + self.headers["Content-Length"] = builtin_str(length) + else: + self.headers["Transfer-Encoding"] = "chunked" + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, "read"): + content_type = None + else: + content_type = "application/x-www-form-urlencoded" + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ("content-type" not in self.headers): + self.headers["Content-Type"] = content_type + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + if length: + # If length exists, set it. Otherwise, we fallback + # to Transfer-Encoding: chunked. + self.headers["Content-Length"] = builtin_str(length) + elif ( + self.method not in ("GET", "HEAD") + and self.headers.get("Content-Length") is None + ): + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) + self.headers["Content-Length"] = "0" + + def prepare_auth(self, auth, url=""): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest ` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers["Cookie"] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response: + """The :class:`Response ` object, which contains a + server's response to an HTTP request. + """ + + __attrs__ = [ + "_content", + "status_code", + "headers", + "url", + "history", + "encoding", + "reason", + "cookies", + "elapsed", + "request", + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + #: This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response ` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest ` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, "_content_consumed", True) + setattr(self, "raw", None) + + def __repr__(self): + return f"" + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return "location" in self.headers and self.status_code in REDIRECT_STATI + + @property + def is_permanent_redirect(self): + """True if this Response one of the permanent versions of redirect.""" + return "location" in self.headers and self.status_code in ( + codes.moved_permanently, + codes.permanent_redirect, + ) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" + return chardet.detect(self.content)["encoding"] + + def iter_content(self, chunk_size=1, decode_unicode=False): + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, "stream"): + try: + yield from self.raw.stream(chunk_size, decode_content=True) + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + except SSLError as e: + raise RequestsSSLError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + elif chunk_size is not None and not isinstance(chunk_size, int): + raise TypeError( + f"chunk_size must be an int, it is instead a {type(chunk_size)}." + ) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + def iter_lines( + self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None + ): + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content( + chunk_size=chunk_size, decode_unicode=decode_unicode + ): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + yield from lines + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_consumed: + raise RuntimeError("The content for this response was already consumed") + + if self.status_code == 0 or self.raw is None: + self._content = None + else: + self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b"" + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``charset_normalizer`` or ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return "" + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding, errors="replace") + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors="replace") + + return content + + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises requests.exceptions.JSONDecodeError: If the response body does not + contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using charset_normalizer to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads(self.content.decode(encoding), **kwargs) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + except JSONDecodeError as e: + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + try: + return complexjson.loads(self.text, **kwargs) + except JSONDecodeError as e: + # Catch JSON-related errors and raise as requests.JSONDecodeError + # This aliases json.JSONDecodeError and simplejson.JSONDecodeError + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get("link") + + resolved_links = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get("rel") or link.get("url") + resolved_links[key] = link + + return resolved_links + + def raise_for_status(self): + """Raises :class:`HTTPError`, if one occurred.""" + + http_error_msg = "" + if isinstance(self.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. (See PR #3538) + try: + reason = self.reason.decode("utf-8") + except UnicodeDecodeError: + reason = self.reason.decode("iso-8859-1") + else: + reason = self.reason + + if 400 <= self.status_code < 500: + http_error_msg = ( + f"{self.status_code} Client Error: {reason} for url: {self.url}" + ) + + elif 500 <= self.status_code < 600: + http_error_msg = ( + f"{self.status_code} Server Error: {reason} for url: {self.url}" + ) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, "release_conn", None) + if release_conn is not None: + release_conn() diff --git a/lib/requests/packages.py b/lib/requests/packages.py new file mode 100644 index 0000000..77c45c9 --- /dev/null +++ b/lib/requests/packages.py @@ -0,0 +1,28 @@ +import sys + +try: + import chardet +except ImportError: + import warnings + + import charset_normalizer as chardet + + warnings.filterwarnings("ignore", "Trying to detect", module="charset_normalizer") + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ("urllib3", "idna"): + locals()[package] = __import__(package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == package or mod.startswith(f"{package}."): + sys.modules[f"requests.packages.{mod}"] = sys.modules[mod] + +target = chardet.__name__ +for mod in list(sys.modules): + if mod == target or mod.startswith(f"{target}."): + target = target.replace(target, "chardet") + sys.modules[f"requests.packages.{target}"] = sys.modules[mod] +# Kinda cool, though, right? diff --git a/lib/requests/sessions.py b/lib/requests/sessions.py new file mode 100644 index 0000000..6cb3b4d --- /dev/null +++ b/lib/requests/sessions.py @@ -0,0 +1,831 @@ +""" +requests.sessions +~~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" +import os +import sys +import time +from collections import OrderedDict +from datetime import timedelta + +from ._internal_utils import to_native_string +from .adapters import HTTPAdapter +from .auth import _basic_auth_str +from .compat import Mapping, cookielib, urljoin, urlparse +from .cookies import ( + RequestsCookieJar, + cookiejar_from_dict, + extract_cookies_to_jar, + merge_cookies, +) +from .exceptions import ( + ChunkedEncodingError, + ContentDecodingError, + InvalidSchema, + TooManyRedirects, +) +from .hooks import default_hooks, dispatch_hook + +# formerly defined here, reexposed here for backward compatibility +from .models import ( # noqa: F401 + DEFAULT_REDIRECT_LIMIT, + REDIRECT_STATI, + PreparedRequest, + Request, +) +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( # noqa: F401 + DEFAULT_PORTS, + default_headers, + get_auth_from_url, + get_environ_proxies, + get_netrc_auth, + requote_uri, + resolve_proxies, + rewind_body, + should_bypass_proxies, + to_key_val_list, +) + +# Preferred clock, based on which one is more accurate on a given system. +if sys.platform == "win32": + preferred_clock = time.perf_counter +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get("response") == []: + return request_hooks + + if request_hooks is None or request_hooks.get("response") == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin: + def get_redirect_target(self, resp): + """Receives a Response. Returns a redirect URI or ``None``""" + # Due to the nature of how requests processes redirects this method will + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + location = resp.headers["location"] + # Currently the underlying http module on py3 decode headers + # in latin1, but empirical evidence suggests that latin1 is very + # rarely used with non-ASCII characters in HTTP headers. + # It is more likely to get UTF8 header rather than latin1. + # This causes incorrect handling of UTF8 encoded location headers. + # To solve this, we re-encode the location in latin1. + location = location.encode("latin1") + return to_native_string(location, "utf8") + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # ports. This isn't specified by RFC 7235, but is kept to avoid + # breaking backwards compatibility with older versions of requests + # that allowed any redirects on the same host. + if ( + old_parsed.scheme == "http" + and old_parsed.port in (80, None) + and new_parsed.scheme == "https" + and new_parsed.port in (443, None) + ): + return False + + # Handle default port usage corresponding to scheme. + changed_port = old_parsed.port != new_parsed.port + changed_scheme = old_parsed.scheme != new_parsed.scheme + default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) + if ( + not changed_scheme + and old_parsed.port in default_port + and new_parsed.port in default_port + ): + return False + + # Standard case: root URI must match + return changed_port or changed_scheme + + def resolve_redirects( + self, + resp, + req, + stream=False, + timeout=None, + verify=True, + cert=None, + proxies=None, + yield_requests=False, + **adapter_kwargs, + ): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects( + f"Exceeded {self.max_redirects} redirects.", response=resp + ) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith("//"): + parsed_rurl = urlparse(resp.url) + url = ":".join([to_native_string(parsed_rurl.scheme), url]) + + # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) + parsed = urlparse(url) + if parsed.fragment == "" and previous_fragment: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/psf/requests/issues/1084 + if resp.status_code not in ( + codes.temporary_redirect, + codes.permanent_redirect, + ): + # https://github.com/psf/requests/issues/3490 + purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding") + for header in purged_headers: + prepared_request.headers.pop(header, None) + prepared_request.body = None + + headers = prepared_request.headers + headers.pop("Cookie", None) + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # A failed tell() sets `_body_position` to `object()`. This non-None + # value ensures `rewindable` will be True, allowing us to raise an + # UnrewindableBodyError, instead of hanging the connection. + rewindable = prepared_request._body_position is not None and ( + "Content-Length" in headers or "Transfer-Encoding" in headers + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs, + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if "Authorization" in headers and self.should_strip_auth( + response.request.url, url + ): + # If we get redirected to a new host, we should strip out any + # authentication headers. + del headers["Authorization"] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + def rebuild_proxies(self, prepared_request, proxies): + """This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :rtype: dict + """ + headers = prepared_request.headers + scheme = urlparse(prepared_request.url).scheme + new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) + + if "Proxy-Authorization" in headers: + del headers["Proxy-Authorization"] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + if username and password: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = prepared_request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.see_other and method != "HEAD": + method = "GET" + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if response.status_code == codes.found and method != "HEAD": + method = "GET" + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if response.status_code == codes.moved and method == "POST": + method = "GET" + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + + + Or as a context manager:: + + >>> with requests.Session() as s: + ... s.get('https://httpbin.org/get') + + """ + + __attrs__ = [ + "headers", + "cookies", + "auth", + "proxies", + "hooks", + "params", + "verify", + "cert", + "adapters", + "stream", + "trust_env", + "max_redirects", + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request ` sent from this + #: :class:`Session `. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request `. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request `. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar `, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount("https://", HTTPAdapter()) + self.mount("http://", HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest ` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request ` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies + ) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting( + request.headers, self.headers, dict_class=CaseInsensitiveDict + ), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request( + self, + method, + url, + params=None, + data=None, + headers=None, + cookies=None, + files=None, + auth=None, + timeout=None, + allow_redirects=True, + proxies=None, + hooks=None, + stream=None, + verify=None, + cert=None, + json=None, + ): + """Constructs a :class:`Request `, prepares it and sends it. + Returns :class:`Response ` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + "timeout": timeout, + "allow_redirects": allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("GET", url, **kwargs) + + def options(self, url, **kwargs): + r"""Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("OPTIONS", url, **kwargs) + + def head(self, url, **kwargs): + r"""Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return self.request("HEAD", url, **kwargs) + + def post(self, url, data=None, json=None, **kwargs): + r"""Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("POST", url, data=data, json=json, **kwargs) + + def put(self, url, data=None, **kwargs): + r"""Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PUT", url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PATCH", url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("DELETE", url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault("stream", self.stream) + kwargs.setdefault("verify", self.verify) + kwargs.setdefault("cert", self.cert) + if "proxies" not in kwargs: + kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if isinstance(request, Request): + raise ValueError("You can only send PreparedRequests.") + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop("allow_redirects", True) + stream = kwargs.get("stream") + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook("response", hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Resolve redirects if allowed. + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # If redirects aren't being followed, store the response on the Request for Response.next(). + if not allow_redirects: + try: + r._next = next( + self.resolve_redirects(r, request, yield_requests=True, **kwargs) + ) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + no_proxy = proxies.get("no_proxy") if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) + for (k, v) in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration + # and be compatible with cURL. + if verify is True or verify is None: + verify = ( + os.environ.get("REQUESTS_CA_BUNDLE") + or os.environ.get("CURL_CA_BUNDLE") + or verify + ) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema(f"No connection adapters were found for {url!r}") + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/lib/requests/status_codes.py b/lib/requests/status_codes.py new file mode 100644 index 0000000..4bd072b --- /dev/null +++ b/lib/requests/status_codes.py @@ -0,0 +1,128 @@ +r""" +The ``codes`` object defines a mapping from common names for HTTP statuses +to their numerical codes, accessible either as attributes or as dictionary +items. + +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +from .structures import LookupDict + +_codes = { + # Informational. + 100: ("continue",), + 101: ("switching_protocols",), + 102: ("processing",), + 103: ("checkpoint",), + 122: ("uri_too_long", "request_uri_too_long"), + 200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"), + 201: ("created",), + 202: ("accepted",), + 203: ("non_authoritative_info", "non_authoritative_information"), + 204: ("no_content",), + 205: ("reset_content", "reset"), + 206: ("partial_content", "partial"), + 207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"), + 208: ("already_reported",), + 226: ("im_used",), + # Redirection. + 300: ("multiple_choices",), + 301: ("moved_permanently", "moved", "\\o-"), + 302: ("found",), + 303: ("see_other", "other"), + 304: ("not_modified",), + 305: ("use_proxy",), + 306: ("switch_proxy",), + 307: ("temporary_redirect", "temporary_moved", "temporary"), + 308: ( + "permanent_redirect", + "resume_incomplete", + "resume", + ), # "resume" and "resume_incomplete" to be removed in 3.0 + # Client Error. + 400: ("bad_request", "bad"), + 401: ("unauthorized",), + 402: ("payment_required", "payment"), + 403: ("forbidden",), + 404: ("not_found", "-o-"), + 405: ("method_not_allowed", "not_allowed"), + 406: ("not_acceptable",), + 407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"), + 408: ("request_timeout", "timeout"), + 409: ("conflict",), + 410: ("gone",), + 411: ("length_required",), + 412: ("precondition_failed", "precondition"), + 413: ("request_entity_too_large",), + 414: ("request_uri_too_large",), + 415: ("unsupported_media_type", "unsupported_media", "media_type"), + 416: ( + "requested_range_not_satisfiable", + "requested_range", + "range_not_satisfiable", + ), + 417: ("expectation_failed",), + 418: ("im_a_teapot", "teapot", "i_am_a_teapot"), + 421: ("misdirected_request",), + 422: ("unprocessable_entity", "unprocessable"), + 423: ("locked",), + 424: ("failed_dependency", "dependency"), + 425: ("unordered_collection", "unordered"), + 426: ("upgrade_required", "upgrade"), + 428: ("precondition_required", "precondition"), + 429: ("too_many_requests", "too_many"), + 431: ("header_fields_too_large", "fields_too_large"), + 444: ("no_response", "none"), + 449: ("retry_with", "retry"), + 450: ("blocked_by_windows_parental_controls", "parental_controls"), + 451: ("unavailable_for_legal_reasons", "legal_reasons"), + 499: ("client_closed_request",), + # Server Error. + 500: ("internal_server_error", "server_error", "/o\\", "✗"), + 501: ("not_implemented",), + 502: ("bad_gateway",), + 503: ("service_unavailable", "unavailable"), + 504: ("gateway_timeout",), + 505: ("http_version_not_supported", "http_version"), + 506: ("variant_also_negotiates",), + 507: ("insufficient_storage",), + 509: ("bandwidth_limit_exceeded", "bandwidth"), + 510: ("not_extended",), + 511: ("network_authentication_required", "network_auth", "network_authentication"), +} + +codes = LookupDict(name="status_codes") + + +def _init(): + for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith(("\\", "/")): + setattr(codes, title.upper(), code) + + def doc(code): + names = ", ".join(f"``{n}``" for n in _codes[code]) + return "* %d: %s" % (code, names) + + global __doc__ + __doc__ = ( + __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes)) + if __doc__ is not None + else None + ) + + +_init() diff --git a/lib/requests/structures.py b/lib/requests/structures.py new file mode 100644 index 0000000..188e13e --- /dev/null +++ b/lib/requests/structures.py @@ -0,0 +1,99 @@ +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from collections import OrderedDict + +from .compat import Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items()) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super().__init__() + + def __repr__(self): + return f"" + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/lib/requests/utils.py b/lib/requests/utils.py new file mode 100644 index 0000000..ad53583 --- /dev/null +++ b/lib/requests/utils.py @@ -0,0 +1,1086 @@ +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile +from collections import OrderedDict + +from urllib3.util import make_headers, parse_url + +from . import certs +from .__version__ import __version__ + +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import HEADER_VALIDATORS, to_native_string # noqa: F401 +from .compat import ( + Mapping, + basestring, + bytes, + getproxies, + getproxies_environment, + integer_types, +) +from .compat import parse_http_list as _parse_list_header +from .compat import ( + proxy_bypass, + proxy_bypass_environment, + quote, + str, + unquote, + urlparse, + urlunparse, +) +from .cookies import cookiejar_from_dict +from .exceptions import ( + FileModeWarning, + InvalidHeader, + InvalidURL, + UnrewindableBodyError, +) +from .structures import CaseInsensitiveDict + +NETRC_FILES = (".netrc", "_netrc") + +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {"http": 80, "https": 443} + +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + + +if sys.platform == "win32": + # provide a proxy_bypass version on Windows without DNS lookups + + def proxy_bypass_registry(host): + try: + import winreg + except ImportError: + return False + + try: + internetSettings = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", + ) + # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it + proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0] + except (OSError, ValueError): + return False + if not proxyEnable or not proxyOverride: + return False + + # make a check value list from the registry entry: replace the + # '' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(";") + # now check if we match one of the registry values. + for test in proxyOverride: + if test == "": + if "." not in host: + return True + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, "items"): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if hasattr(o, "__len__"): + total_length = len(o) + + elif hasattr(o, "len"): + total_length = o.len + + elif hasattr(o, "fileno"): + try: + fileno = o.fileno() + except (io.UnsupportedOperation, AttributeError): + # AttributeError is a surprising exception, seeing as how we've just checked + # that `hasattr(o, 'fileno')`. It happens for objects obtained via + # `Tarfile.extractfile()`, per issue 5229. + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if "b" not in o.mode: + warnings.warn( + ( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode." + ), + FileModeWarning, + ) + + if hasattr(o, "tell"): + try: + current_position = o.tell() + except OSError: + # This can happen in some weird situations, such as when the file + # is actually a special file descriptor like stdin. In this + # instance, we don't know what the length is, so set it to zero and + # let requests chunk it instead. + if total_length is not None: + current_position = total_length + else: + if hasattr(o, "seek") and total_length is None: + # StringIO and BytesIO have seek but no usable fileno + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except OSError: + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + netrc_file = os.environ.get("NETRC") + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = (f"~/{f}" for f in NETRC_FILES) + + try: + from netrc import NetrcParseError, netrc + + netrc_path = None + + for f in netrc_locations: + try: + loc = os.path.expanduser(f) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See https://bugs.python.org/issue20164 & + # https://github.com/psf/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b":" + if isinstance(url, str): + splitstr = splitstr.decode("ascii") + host = ri.netloc.split(splitstr)[0] + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc: + # Return with login / password + login_i = 0 if _netrc[0] else 1 + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, OSError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # App Engine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj): + """Tries to guess the filename of the given object.""" + name = getattr(obj, "name", None) + if name and isinstance(name, basestring) and name[0] != "<" and name[-1] != ">": + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + archive, member = os.path.split(path) + while archive and not os.path.exists(archive): + archive, prefix = os.path.split(archive) + if not prefix: + # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split), + # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users + break + member = "/".join([prefix, member]) + + if not zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + tmp = tempfile.gettempdir() + extracted_path = os.path.join(tmp, member.split("/")[-1]) + if not os.path.exists(extracted_path): + # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition + with atomic_open(extracted_path) as file_handler: + file_handler.write(zip_file.read(member)) + return extracted_path + + +@contextlib.contextmanager +def atomic_open(filename): + """Write a file to the disk in an atomic fashion""" + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, "wb") as tmp_handler: + yield tmp_handler + os.replace(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if "=" not in item: + result[item] = None + continue + name, value = item.split("=", 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != "\\\\": + return value.replace("\\\\", "\\").replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn( + ( + "In requests 3.0, get_encodings_from_content will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + charset_re = re.compile(r']', flags=re.I) + pragma_re = re.compile(r']', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return ( + charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content) + ) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header + + :param header: string + :return: tuple containing content type and dictionary of + parameters + """ + + tokens = header.split(";") + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + items_to_strip = "\"' " + + for param in params: + param = param.strip() + if param: + key, value = param, True + index_of_equals = param.find("=") + if index_of_equals != -1: + key = param[:index_of_equals].strip(items_to_strip) + value = param[index_of_equals + 1 :].strip(items_to_strip) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + content_type = headers.get("content-type") + + if not content_type: + return None + + content_type, params = _parse_content_type_header(content_type) + + if "charset" in params: + return params["charset"].strip("'\"") + + if "text" in content_type: + return "ISO-8859-1" + + if "application/json" in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return "utf-8" + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes an iterator.""" + + if r.encoding is None: + yield from iterator + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace") + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b"", final=True) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos : pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :rtype: str + """ + warnings.warn( + ( + "In requests 3.0, get_unicode_from_response will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors="replace") + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~" +) + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + parts = uri.split("%") + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL(f"Invalid percent-escape sequence: '{h}'") + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = f"%{parts[i]}" + else: + parts[i] = f"%{parts[i]}" + return "".join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :rtype: bool + """ + ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0] + netaddr, bits = net.split("/") + netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack(">I", bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except OSError: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count("/") == 1: + try: + mask = int(string_network.split("/")[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split("/")[0]) + except OSError: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + # Prioritize lowercase environment variables over uppercase + # to keep a consistent behaviour with other http projects (curl, wget). + def get_proxy(key): + return os.environ.get(key) or os.environ.get(key.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy("no_proxy") + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the hostname, both with and without the port. + no_proxy = (host for host in no_proxy.replace(" ", "").split(",") if host) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += f":{parsed.port}" + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + with set_environ("no_proxy", no_proxy_arg): + # parsed.hostname can be `None` in cases such as a file URI. + try: + bypass = proxy_bypass(parsed.hostname) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get("all")) + + proxy_keys = [ + urlparts.scheme + "://" + urlparts.hostname, + urlparts.scheme, + "all://" + urlparts.hostname, + "all", + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def resolve_proxies(request, proxies, trust_env=True): + """This method takes proxy information from a request and configuration + input to resolve a mapping of target proxies. This will consider settings + such a NO_PROXY to strip proxy configurations. + + :param request: Request or PreparedRequest + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + :param trust_env: Boolean declaring whether to trust environment configs + + :rtype: dict + """ + proxies = proxies if proxies is not None else {} + url = request.url + scheme = urlparse(url).scheme + no_proxy = proxies.get("no_proxy") + new_proxies = proxies.copy() + + if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy): + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) + + proxy = environ_proxies.get(scheme, environ_proxies.get("all")) + + if proxy: + new_proxies.setdefault(scheme, proxy) + return new_proxies + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return f"{name}/{__version__}" + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict( + { + "User-Agent": default_user_agent(), + "Accept-Encoding": DEFAULT_ACCEPT_ENCODING, + "Accept": "*/*", + "Connection": "keep-alive", + } + ) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: ; rel=front; type="image/jpeg",; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = " '\"" + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(", *<", value): + try: + url, params = val.split(";", 1) + except ValueError: + url, params = val, "" + + link = {"url": url.strip("<> '\"")} + + for param in params.split(";"): + try: + key, value = param.split("=") + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = "\x00".encode("ascii") # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): + return "utf-32" # BOM included + if sample[:3] == codecs.BOM_UTF8: + return "utf-8-sig" # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return "utf-16" # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return "utf-8" + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return "utf-16-be" + if sample[1::2] == _null2: # 2nd and 4th are null + return "utf-16-le" + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return "utf-32-be" + if sample[1:] == _null3: + return "utf-32-le" + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :rtype: str + """ + parsed = parse_url(url) + scheme, auth, host, port, path, query, fragment = parsed + + # A defect in urlparse determines that there isn't a netloc present in some + # urls. We previously assumed parsing was overly cautious, and swapped the + # netloc and path. Due to a lack of tests on the original defect, this is + # maintained with parse_url for backwards compatibility. + netloc = parsed.netloc + if not netloc: + netloc, path = path, netloc + + if auth: + # parse_url doesn't provide the netloc with auth + # so we'll add it ourselves. + netloc = "@".join([auth, netloc]) + if scheme is None: + scheme = new_scheme + if path is None: + path = "" + + return urlunparse((scheme, netloc, path, "", query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + auth = ("", "") + + return auth + + +def check_header_validity(header): + """Verifies that header parts don't contain leading whitespace + reserved characters, or return characters. + + :param header: tuple, in the format (name, value). + """ + name, value = header + + for part in header: + if type(part) not in HEADER_VALIDATORS: + raise InvalidHeader( + f"Header part ({part!r}) from {{{name!r}: {value!r}}} must be " + f"of type str or bytes, not {type(part)}" + ) + + _validate_header_part(name, "name", HEADER_VALIDATORS[type(name)][0]) + _validate_header_part(value, "value", HEADER_VALIDATORS[type(value)][1]) + + +def _validate_header_part(header_part, header_kind, validator): + if not validator.match(header_part): + raise InvalidHeader( + f"Invalid leading whitespace, reserved character(s), or return" + f"character(s) in header {header_kind}: {header_part!r}" + ) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit("@", 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, "")) + + +def rewind_body(prepared_request): + """Move file pointer back to its recorded starting position + so it can be read again on redirect. + """ + body_seek = getattr(prepared_request.body, "seek", None) + if body_seek is not None and isinstance( + prepared_request._body_position, integer_types + ): + try: + body_seek(prepared_request._body_position) + except OSError: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect." + ) + else: + raise UnrewindableBodyError("Unable to rewind request body for redirect.") diff --git a/lib/tqdm-4.65.0.dist-info/INSTALLER b/lib/tqdm-4.65.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/tqdm-4.65.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/tqdm-4.65.0.dist-info/LICENCE b/lib/tqdm-4.65.0.dist-info/LICENCE new file mode 100644 index 0000000..6a370c5 --- /dev/null +++ b/lib/tqdm-4.65.0.dist-info/LICENCE @@ -0,0 +1,49 @@ +`tqdm` is a product of collaborative work. +Unless otherwise stated, all authors (see commit logs) retain copyright +for their respective work, and release the work under the MIT licence +(text below). + +Exceptions or notable authors are listed below +in reverse chronological order: + +* files: * + MPLv2.0 2015-2023 (c) Casper da Costa-Luis + [casperdcl](https://github.com/casperdcl). +* files: tqdm/_tqdm.py + MIT 2016 (c) [PR #96] on behalf of Google Inc. +* files: tqdm/_tqdm.py setup.py README.rst MANIFEST.in .gitignore + MIT 2013 (c) Noam Yorav-Raphael, original author. + +[PR #96]: https://github.com/tqdm/tqdm/pull/96 + + +Mozilla Public Licence (MPL) v. 2.0 - Exhibit A +----------------------------------------------- + +This Source Code Form is subject to the terms of the +Mozilla Public License, v. 2.0. +If a copy of the MPL was not distributed with this project, +You can obtain one at https://mozilla.org/MPL/2.0/. + + +MIT License (MIT) +----------------- + +Copyright (c) 2013 noamraph + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/tqdm-4.65.0.dist-info/METADATA b/lib/tqdm-4.65.0.dist-info/METADATA new file mode 100644 index 0000000..780ecae --- /dev/null +++ b/lib/tqdm-4.65.0.dist-info/METADATA @@ -0,0 +1,1581 @@ +Metadata-Version: 2.1 +Name: tqdm +Version: 4.65.0 +Summary: Fast, Extensible Progress Meter +Home-page: https://tqdm.github.io +Maintainer: tqdm developers +Maintainer-email: python.tqdm@gmail.com +License: MPLv2.0, MIT Licences +Project-URL: Changelog, https://tqdm.github.io/releases +Project-URL: Source, https://github.com/tqdm/tqdm +Project-URL: Wiki, https://github.com/tqdm/tqdm/wiki +Keywords: progressbar,progressmeter,progress,bar,meter,rate,eta,console,terminal,time +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Environment :: MacOS X +Classifier: Environment :: Other Environment +Classifier: Environment :: Win32 (MS Windows) +Classifier: Environment :: X11 Applications +Classifier: Framework :: IPython +Classifier: Framework :: Jupyter +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Education +Classifier: Intended Audience :: End Users/Desktop +Classifier: Intended Audience :: Other Audience +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: MIT License +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Operating System :: MacOS +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft +Classifier: Operating System :: Microsoft :: MS-DOS +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Operating System :: POSIX :: BSD +Classifier: Operating System :: POSIX :: BSD :: FreeBSD +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: POSIX :: SunOS/Solaris +Classifier: Operating System :: Unix +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation +Classifier: Programming Language :: Python :: Implementation :: IronPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Unix Shell +Classifier: Topic :: Desktop Environment +Classifier: Topic :: Education :: Computer Aided Instruction (CAI) +Classifier: Topic :: Education :: Testing +Classifier: Topic :: Office/Business +Classifier: Topic :: Other/Nonlisted Topic +Classifier: Topic :: Software Development :: Build Tools +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Software Development :: Pre-processors +Classifier: Topic :: Software Development :: User Interfaces +Classifier: Topic :: System :: Installation/Setup +Classifier: Topic :: System :: Logging +Classifier: Topic :: System :: Monitoring +Classifier: Topic :: System :: Shells +Classifier: Topic :: Terminals +Classifier: Topic :: Utilities +Provides: tqdm +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENCE +Requires-Dist: colorama ; platform_system == "Windows" +Provides-Extra: dev +Requires-Dist: py-make (>=0.1.0) ; extra == 'dev' +Requires-Dist: twine ; extra == 'dev' +Requires-Dist: wheel ; extra == 'dev' +Provides-Extra: notebook +Requires-Dist: ipywidgets (>=6) ; extra == 'notebook' +Provides-Extra: slack +Requires-Dist: slack-sdk ; extra == 'slack' +Provides-Extra: telegram +Requires-Dist: requests ; extra == 'telegram' + +|Logo| + +tqdm +==== + +|Py-Versions| |Versions| |Conda-Forge-Status| |Docker| |Snapcraft| + +|Build-Status| |Coverage-Status| |Branch-Coverage-Status| |Codacy-Grade| |Libraries-Rank| |PyPI-Downloads| + +|LICENCE| |OpenHub-Status| |binder-demo| |awesome-python| + +``tqdm`` derives from the Arabic word *taqaddum* (تقدّم) which can mean "progress," +and is an abbreviation for "I love you so much" in Spanish (*te quiero demasiado*). + +Instantly make your loops show a smart progress meter - just wrap any +iterable with ``tqdm(iterable)``, and you're done! + +.. code:: python + + from tqdm import tqdm + for i in tqdm(range(10000)): + ... + +``76%|████████████████████████        | 7568/10000 [00:33<00:10, 229.00it/s]`` + +``trange(N)`` can be also used as a convenient shortcut for +``tqdm(range(N))``. + +|Screenshot| + |Video| |Slides| |Merch| + +It can also be executed as a module with pipes: + +.. code:: sh + + $ seq 9999999 | tqdm --bytes | wc -l + 75.2MB [00:00, 217MB/s] + 9999999 + + $ tar -zcf - docs/ | tqdm --bytes --total `du -sb docs/ | cut -f1` \ + > backup.tgz + 32%|██████████▍ | 8.89G/27.9G [00:42<01:31, 223MB/s] + +Overhead is low -- about 60ns per iteration (80ns with ``tqdm.gui``), and is +unit tested against performance regression. +By comparison, the well-established +`ProgressBar `__ has +an 800ns/iter overhead. + +In addition to its low overhead, ``tqdm`` uses smart algorithms to predict +the remaining time and to skip unnecessary iteration displays, which allows +for a negligible overhead in most cases. + +``tqdm`` works on any platform +(Linux, Windows, Mac, FreeBSD, NetBSD, Solaris/SunOS), +in any console or in a GUI, and is also friendly with IPython/Jupyter notebooks. + +``tqdm`` does not require any dependencies (not even ``curses``!), just +Python and an environment supporting ``carriage return \r`` and +``line feed \n`` control characters. + +------------------------------------------ + +.. contents:: Table of contents + :backlinks: top + :local: + + +Installation +------------ + +Latest PyPI stable release +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +|Versions| |PyPI-Downloads| |Libraries-Dependents| + +.. code:: sh + + pip install tqdm + +Latest development release on GitHub +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +|GitHub-Status| |GitHub-Stars| |GitHub-Commits| |GitHub-Forks| |GitHub-Updated| + +Pull and install pre-release ``devel`` branch: + +.. code:: sh + + pip install "git+https://github.com/tqdm/tqdm.git@devel#egg=tqdm" + +Latest Conda release +~~~~~~~~~~~~~~~~~~~~ + +|Conda-Forge-Status| + +.. code:: sh + + conda install -c conda-forge tqdm + +Latest Snapcraft release +~~~~~~~~~~~~~~~~~~~~~~~~ + +|Snapcraft| + +There are 3 channels to choose from: + +.. code:: sh + + snap install tqdm # implies --stable, i.e. latest tagged release + snap install tqdm --candidate # master branch + snap install tqdm --edge # devel branch + +Note that ``snap`` binaries are purely for CLI use (not ``import``-able), and +automatically set up ``bash`` tab-completion. + +Latest Docker release +~~~~~~~~~~~~~~~~~~~~~ + +|Docker| + +.. code:: sh + + docker pull tqdm/tqdm + docker run -i --rm tqdm/tqdm --help + +Other +~~~~~ + +There are other (unofficial) places where ``tqdm`` may be downloaded, particularly for CLI use: + +|Repology| + +.. |Repology| image:: https://repology.org/badge/tiny-repos/python:tqdm.svg + :target: https://repology.org/project/python:tqdm/versions + +Changelog +--------- + +The list of all changes is available either on GitHub's Releases: +|GitHub-Status|, on the +`wiki `__, or on the +`website `__. + + +Usage +----- + +``tqdm`` is very versatile and can be used in a number of ways. +The three main ones are given below. + +Iterable-based +~~~~~~~~~~~~~~ + +Wrap ``tqdm()`` around any iterable: + +.. code:: python + + from tqdm import tqdm + from time import sleep + + text = "" + for char in tqdm(["a", "b", "c", "d"]): + sleep(0.25) + text = text + char + +``trange(i)`` is a special optimised instance of ``tqdm(range(i))``: + +.. code:: python + + from tqdm import trange + + for i in trange(100): + sleep(0.01) + +Instantiation outside of the loop allows for manual control over ``tqdm()``: + +.. code:: python + + pbar = tqdm(["a", "b", "c", "d"]) + for char in pbar: + sleep(0.25) + pbar.set_description("Processing %s" % char) + +Manual +~~~~~~ + +Manual control of ``tqdm()`` updates using a ``with`` statement: + +.. code:: python + + with tqdm(total=100) as pbar: + for i in range(10): + sleep(0.1) + pbar.update(10) + +If the optional variable ``total`` (or an iterable with ``len()``) is +provided, predictive stats are displayed. + +``with`` is also optional (you can just assign ``tqdm()`` to a variable, +but in this case don't forget to ``del`` or ``close()`` at the end: + +.. code:: python + + pbar = tqdm(total=100) + for i in range(10): + sleep(0.1) + pbar.update(10) + pbar.close() + +Module +~~~~~~ + +Perhaps the most wonderful use of ``tqdm`` is in a script or on the command +line. Simply inserting ``tqdm`` (or ``python -m tqdm``) between pipes will pass +through all ``stdin`` to ``stdout`` while printing progress to ``stderr``. + +The example below demonstrate counting the number of lines in all Python files +in the current directory, with timing information included. + +.. code:: sh + + $ time find . -name '*.py' -type f -exec cat \{} \; | wc -l + 857365 + + real 0m3.458s + user 0m0.274s + sys 0m3.325s + + $ time find . -name '*.py' -type f -exec cat \{} \; | tqdm | wc -l + 857366it [00:03, 246471.31it/s] + 857365 + + real 0m3.585s + user 0m0.862s + sys 0m3.358s + +Note that the usual arguments for ``tqdm`` can also be specified. + +.. code:: sh + + $ find . -name '*.py' -type f -exec cat \{} \; | + tqdm --unit loc --unit_scale --total 857366 >> /dev/null + 100%|█████████████████████████████████| 857K/857K [00:04<00:00, 246Kloc/s] + +Backing up a large directory? + +.. code:: sh + + $ tar -zcf - docs/ | tqdm --bytes --total `du -sb docs/ | cut -f1` \ + > backup.tgz + 44%|██████████████▊ | 153M/352M [00:14<00:18, 11.0MB/s] + +This can be beautified further: + +.. code:: sh + + $ BYTES="$(du -sb docs/ | cut -f1)" + $ tar -cf - docs/ \ + | tqdm --bytes --total "$BYTES" --desc Processing | gzip \ + | tqdm --bytes --total "$BYTES" --desc Compressed --position 1 \ + > ~/backup.tgz + Processing: 100%|██████████████████████| 352M/352M [00:14<00:00, 30.2MB/s] + Compressed: 42%|█████████▎ | 148M/352M [00:14<00:19, 10.9MB/s] + +Or done on a file level using 7-zip: + +.. code:: sh + + $ 7z a -bd -r backup.7z docs/ | grep Compressing \ + | tqdm --total $(find docs/ -type f | wc -l) --unit files \ + | grep -v Compressing + 100%|██████████████████████████▉| 15327/15327 [01:00<00:00, 712.96files/s] + +Pre-existing CLI programs already outputting basic progress information will +benefit from ``tqdm``'s ``--update`` and ``--update_to`` flags: + +.. code:: sh + + $ seq 3 0.1 5 | tqdm --total 5 --update_to --null + 100%|████████████████████████████████████| 5.0/5 [00:00<00:00, 9673.21it/s] + $ seq 10 | tqdm --update --null # 1 + 2 + ... + 10 = 55 iterations + 55it [00:00, 90006.52it/s] + +FAQ and Known Issues +-------------------- + +|GitHub-Issues| + +The most common issues relate to excessive output on multiple lines, instead +of a neat one-line progress bar. + +- Consoles in general: require support for carriage return (``CR``, ``\r``). +- Nested progress bars: + + * Consoles in general: require support for moving cursors up to the + previous line. For example, + `IDLE `__, + `ConEmu `__ and + `PyCharm `__ (also + `here `__, + `here `__, and + `here `__) + lack full support. + * Windows: additionally may require the Python module ``colorama`` + to ensure nested bars stay within their respective lines. + +- Unicode: + + * Environments which report that they support unicode will have solid smooth + progressbars. The fallback is an ``ascii``-only bar. + * Windows consoles often only partially support unicode and thus + `often require explicit ascii=True `__ + (also `here `__). This is due to + either normal-width unicode characters being incorrectly displayed as + "wide", or some unicode characters not rendering. + +- Wrapping generators: + + * Generator wrapper functions tend to hide the length of iterables. + ``tqdm`` does not. + * Replace ``tqdm(enumerate(...))`` with ``enumerate(tqdm(...))`` or + ``tqdm(enumerate(x), total=len(x), ...)``. + The same applies to ``numpy.ndenumerate``. + * Replace ``tqdm(zip(a, b))`` with ``zip(tqdm(a), b)`` or even + ``zip(tqdm(a), tqdm(b))``. + * The same applies to ``itertools``. + * Some useful convenience functions can be found under ``tqdm.contrib``. + +- `Hanging pipes in python2 `__: + when using ``tqdm`` on the CLI, you may need to use Python 3.5+ for correct + buffering. +- `No intermediate output in docker-compose `__: + use ``docker-compose run`` instead of ``docker-compose up`` and ``tty: true``. + +If you come across any other difficulties, browse and file |GitHub-Issues|. + +Documentation +------------- + +|Py-Versions| |README-Hits| (Since 19 May 2016) + +.. code:: python + + class tqdm(): + """ + Decorate an iterable object, returning an iterator which acts exactly + like the original iterable, but prints a dynamically updating + progressbar every time a value is requested. + """ + + def __init__(self, iterable=None, desc=None, total=None, leave=True, + file=None, ncols=None, mininterval=0.1, + maxinterval=10.0, miniters=None, ascii=None, disable=False, + unit='it', unit_scale=False, dynamic_ncols=False, + smoothing=0.3, bar_format=None, initial=0, position=None, + postfix=None, unit_divisor=1000): + +Parameters +~~~~~~~~~~ + +* iterable : iterable, optional + Iterable to decorate with a progressbar. + Leave blank to manually manage the updates. +* desc : str, optional + Prefix for the progressbar. +* total : int or float, optional + The number of expected iterations. If unspecified, + len(iterable) is used if possible. If float("inf") or as a last + resort, only basic progress statistics are displayed + (no ETA, no progressbar). + If ``gui`` is True and this parameter needs subsequent updating, + specify an initial arbitrary large positive number, + e.g. 9e9. +* leave : bool, optional + If [default: True], keeps all traces of the progressbar + upon termination of iteration. + If ``None``, will leave only if ``position`` is ``0``. +* file : ``io.TextIOWrapper`` or ``io.StringIO``, optional + Specifies where to output the progress messages + (default: sys.stderr). Uses ``file.write(str)`` and ``file.flush()`` + methods. For encoding, see ``write_bytes``. +* ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes the progressbar to stay within this bound. + If unspecified, attempts to use environment width. The + fallback is a meter width of 10 and no limit for the counter and + statistics. If 0, will not print any meter (only stats). +* mininterval : float, optional + Minimum progress display update interval [default: 0.1] seconds. +* maxinterval : float, optional + Maximum progress display update interval [default: 10] seconds. + Automatically adjusts ``miniters`` to correspond to ``mininterval`` + after long display update lag. Only works if ``dynamic_miniters`` + or monitor thread is enabled. +* miniters : int or float, optional + Minimum progress display update interval, in iterations. + If 0 and ``dynamic_miniters``, will automatically adjust to equal + ``mininterval`` (more CPU efficient, good for tight loops). + If > 0, will skip display of specified number of iterations. + Tweak this and ``mininterval`` to get very efficient loops. + If your progress is erratic with both fast and slow iterations + (network, skipping items, etc) you should set miniters=1. +* ascii : bool or str, optional + If unspecified or False, use unicode (smooth blocks) to fill + the meter. The fallback is to use ASCII characters " 123456789#". +* disable : bool, optional + Whether to disable the entire progressbar wrapper + [default: False]. If set to None, disable on non-TTY. +* unit : str, optional + String that will be used to define the unit of each iteration + [default: it]. +* unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be reduced/scaled + automatically and a metric prefix following the + International System of Units standard will be added + (kilo, mega, etc.) [default: False]. If any other non-zero + number, will scale ``total`` and ``n``. +* dynamic_ncols : bool, optional + If set, constantly alters ``ncols`` and ``nrows`` to the + environment (allowing for window resizes) [default: False]. +* smoothing : float, optional + Exponential moving average smoothing factor for speed estimates + (ignored in GUI mode). Ranges from 0 (average speed) to 1 + (current/instantaneous speed) [default: 0.3]. +* bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s, eta. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. +* initial : int or float, optional + The initial counter value. Useful when restarting a progress + bar [default: 0]. If using float, consider specifying ``{n:.3f}`` + or similar in ``bar_format``, or specifying ``unit_scale``. +* position : int, optional + Specify the line offset to print this bar (starting from 0) + Automatic if unspecified. + Useful to manage multiple bars at once (eg, from threads). +* postfix : dict or ``*``, optional + Specify additional stats to display at the end of the bar. + Calls ``set_postfix(**postfix)`` if possible (dict). +* unit_divisor : float, optional + [default: 1000], ignored unless ``unit_scale`` is True. +* write_bytes : bool, optional + Whether to write bytes. If (default: False) will write unicode. +* lock_args : tuple, optional + Passed to ``refresh`` for intermediate output + (initialisation, iterating, and updating). +* nrows : int, optional + The screen height. If specified, hides nested bars outside this + bound. If unspecified, attempts to use environment height. + The fallback is 20. +* colour : str, optional + Bar colour (e.g. 'green', '#00ff00'). +* delay : float, optional + Don't display until [default: 0] seconds have elapsed. + +Extra CLI Options +~~~~~~~~~~~~~~~~~ + +* delim : chr, optional + Delimiting character [default: '\n']. Use '\0' for null. + N.B.: on Windows systems, Python converts '\n' to '\r\n'. +* buf_size : int, optional + String buffer size in bytes [default: 256] + used when ``delim`` is specified. +* bytes : bool, optional + If true, will count bytes, ignore ``delim``, and default + ``unit_scale`` to True, ``unit_divisor`` to 1024, and ``unit`` to 'B'. +* tee : bool, optional + If true, passes ``stdin`` to both ``stderr`` and ``stdout``. +* update : bool, optional + If true, will treat input as newly elapsed iterations, + i.e. numbers to pass to ``update()``. Note that this is slow + (~2e5 it/s) since every input must be decoded as a number. +* update_to : bool, optional + If true, will treat input as total elapsed iterations, + i.e. numbers to assign to ``self.n``. Note that this is slow + (~2e5 it/s) since every input must be decoded as a number. +* null : bool, optional + If true, will discard input (no stdout). +* manpath : str, optional + Directory in which to install tqdm man pages. +* comppath : str, optional + Directory in which to place tqdm completion. +* log : str, optional + CRITICAL|FATAL|ERROR|WARN(ING)|[default: 'INFO']|DEBUG|NOTSET. + +Returns +~~~~~~~ + +* out : decorated iterator. + +.. code:: python + + class tqdm(): + def update(self, n=1): + """ + Manually update the progress bar, useful for streams + such as reading files. + E.g.: + >>> t = tqdm(total=filesize) # Initialise + >>> for current_buffer in stream: + ... ... + ... t.update(len(current_buffer)) + >>> t.close() + The last line is highly recommended, but possibly not necessary if + ``t.update()`` will be called in such a way that ``filesize`` will be + exactly reached and printed. + + Parameters + ---------- + n : int or float, optional + Increment to add to the internal counter of iterations + [default: 1]. If using float, consider specifying ``{n:.3f}`` + or similar in ``bar_format``, or specifying ``unit_scale``. + + Returns + ------- + out : bool or None + True if a ``display()`` was triggered. + """ + + def close(self): + """Cleanup and (if leave=False) close the progressbar.""" + + def clear(self, nomove=False): + """Clear current bar display.""" + + def refresh(self): + """ + Force refresh the display of this bar. + + Parameters + ---------- + nolock : bool, optional + If ``True``, does not lock. + If [default: ``False``]: calls ``acquire()`` on internal lock. + lock_args : tuple, optional + Passed to internal lock's ``acquire()``. + If specified, will only ``display()`` if ``acquire()`` returns ``True``. + """ + + def unpause(self): + """Restart tqdm timer from last print time.""" + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with ``leave=True``. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + + def set_description(self, desc=None, refresh=True): + """ + Set/modify description of the progress bar. + + Parameters + ---------- + desc : str, optional + refresh : bool, optional + Forces refresh [default: True]. + """ + + def set_postfix(self, ordered_dict=None, refresh=True, **tqdm_kwargs): + """ + Set/modify postfix (additional stats) + with automatic formatting based on datatype. + + Parameters + ---------- + ordered_dict : dict or OrderedDict, optional + refresh : bool, optional + Forces refresh [default: True]. + kwargs : dict, optional + """ + + @classmethod + def write(cls, s, file=sys.stdout, end="\n"): + """Print a message via tqdm (without overlap with bars).""" + + @property + def format_dict(self): + """Public API for read-only member access.""" + + def display(self, msg=None, pos=None): + """ + Use ``self.sp`` to display ``msg`` in the specified ``pos``. + + Consider overloading this function when inheriting to use e.g.: + ``self.some_frontend(**self.format_dict)`` instead of ``self.sp``. + + Parameters + ---------- + msg : str, optional. What to display (default: ``repr(self)``). + pos : int, optional. Position to ``moveto`` + (default: ``abs(self.pos)``). + """ + + @classmethod + @contextmanager + def wrapattr(cls, stream, method, total=None, bytes=True, **tqdm_kwargs): + """ + stream : file-like object. + method : str, "read" or "write". The result of ``read()`` and + the first argument of ``write()`` should have a ``len()``. + + >>> with tqdm.wrapattr(file_obj, "read", total=file_obj.size) as fobj: + ... while True: + ... chunk = fobj.read(chunk_size) + ... if not chunk: + ... break + """ + + @classmethod + def pandas(cls, *targs, **tqdm_kwargs): + """Registers the current `tqdm` class with `pandas`.""" + + def trange(*args, **tqdm_kwargs): + """Shortcut for `tqdm(range(*args), **tqdm_kwargs)`.""" + +Convenience Functions +~~~~~~~~~~~~~~~~~~~~~ + +.. code:: python + + def tqdm.contrib.tenumerate(iterable, start=0, total=None, + tqdm_class=tqdm.auto.tqdm, **tqdm_kwargs): + """Equivalent of `numpy.ndenumerate` or builtin `enumerate`.""" + + def tqdm.contrib.tzip(iter1, *iter2plus, **tqdm_kwargs): + """Equivalent of builtin `zip`.""" + + def tqdm.contrib.tmap(function, *sequences, **tqdm_kwargs): + """Equivalent of builtin `map`.""" + +Submodules +~~~~~~~~~~ + +.. code:: python + + class tqdm.notebook.tqdm(tqdm.tqdm): + """IPython/Jupyter Notebook widget.""" + + class tqdm.auto.tqdm(tqdm.tqdm): + """Automatically chooses beween `tqdm.notebook` and `tqdm.tqdm`.""" + + class tqdm.asyncio.tqdm(tqdm.tqdm): + """Asynchronous version.""" + @classmethod + def as_completed(cls, fs, *, loop=None, timeout=None, total=None, + **tqdm_kwargs): + """Wrapper for `asyncio.as_completed`.""" + + class tqdm.gui.tqdm(tqdm.tqdm): + """Matplotlib GUI version.""" + + class tqdm.tk.tqdm(tqdm.tqdm): + """Tkinter GUI version.""" + + class tqdm.rich.tqdm(tqdm.tqdm): + """`rich.progress` version.""" + + class tqdm.keras.TqdmCallback(keras.callbacks.Callback): + """Keras callback for epoch and batch progress.""" + + class tqdm.dask.TqdmCallback(dask.callbacks.Callback): + """Dask callback for task progress.""" + + +``contrib`` ++++++++++++ + +The ``tqdm.contrib`` package also contains experimental modules: + +- ``tqdm.contrib.itertools``: Thin wrappers around ``itertools`` +- ``tqdm.contrib.concurrent``: Thin wrappers around ``concurrent.futures`` +- ``tqdm.contrib.slack``: Posts to `Slack `__ bots +- ``tqdm.contrib.discord``: Posts to `Discord `__ bots +- ``tqdm.contrib.telegram``: Posts to `Telegram `__ bots +- ``tqdm.contrib.bells``: Automagically enables all optional features + + * ``auto``, ``pandas``, ``slack``, ``discord``, ``telegram`` + +Examples and Advanced Usage +--------------------------- + +- See the `examples `__ + folder; +- import the module and run ``help()``; +- consult the `wiki `__; + + * this has an + `excellent article `__ + on how to make a **great** progressbar; + +- check out the `slides from PyData London `__, or +- run the |binder-demo|. + +Description and additional stats +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Custom information can be displayed and updated dynamically on ``tqdm`` bars +with the ``desc`` and ``postfix`` arguments: + +.. code:: python + + from tqdm import tqdm, trange + from random import random, randint + from time import sleep + + with trange(10) as t: + for i in t: + # Description will be displayed on the left + t.set_description('GEN %i' % i) + # Postfix will be displayed on the right, + # formatted automatically based on argument's datatype + t.set_postfix(loss=random(), gen=randint(1,999), str='h', + lst=[1, 2]) + sleep(0.1) + + with tqdm(total=10, bar_format="{postfix[0]} {postfix[1][value]:>8.2g}", + postfix=["Batch", {"value": 0}]) as t: + for i in range(10): + sleep(0.1) + t.postfix[1]["value"] = i / 2 + t.update() + +Points to remember when using ``{postfix[...]}`` in the ``bar_format`` string: + +- ``postfix`` also needs to be passed as an initial argument in a compatible + format, and +- ``postfix`` will be auto-converted to a string if it is a ``dict``-like + object. To prevent this behaviour, insert an extra item into the dictionary + where the key is not a string. + +Additional ``bar_format`` parameters may also be defined by overriding +``format_dict``, and the bar itself may be modified using ``ascii``: + +.. code:: python + + from tqdm import tqdm + class TqdmExtraFormat(tqdm): + """Provides a `total_time` format parameter""" + @property + def format_dict(self): + d = super(TqdmExtraFormat, self).format_dict + total_time = d["elapsed"] * (d["total"] or 0) / max(d["n"], 1) + d.update(total_time=self.format_interval(total_time) + " in total") + return d + + for i in TqdmExtraFormat( + range(9), ascii=" .oO0", + bar_format="{total_time}: {percentage:.0f}%|{bar}{r_bar}"): + if i == 4: + break + +.. code:: + + 00:00 in total: 44%|0000. | 4/9 [00:00<00:00, 962.93it/s] + +Note that ``{bar}`` also supports a format specifier ``[width][type]``. + +- ``width`` + + * unspecified (default): automatic to fill ``ncols`` + * ``int >= 0``: fixed width overriding ``ncols`` logic + * ``int < 0``: subtract from the automatic default + +- ``type`` + + * ``a``: ascii (``ascii=True`` override) + * ``u``: unicode (``ascii=False`` override) + * ``b``: blank (``ascii=" "`` override) + +This means a fixed bar with right-justified text may be created by using: +``bar_format="{l_bar}{bar:10}|{bar:-10b}right-justified"`` + +Nested progress bars +~~~~~~~~~~~~~~~~~~~~ + +``tqdm`` supports nested progress bars. Here's an example: + +.. code:: python + + from tqdm.auto import trange + from time import sleep + + for i in trange(4, desc='1st loop'): + for j in trange(5, desc='2nd loop'): + for k in trange(50, desc='3rd loop', leave=False): + sleep(0.01) + +For manual control over positioning (e.g. for multi-processing use), +you may specify ``position=n`` where ``n=0`` for the outermost bar, +``n=1`` for the next, and so on. +However, it's best to check if ``tqdm`` can work without manual ``position`` +first. + +.. code:: python + + from time import sleep + from tqdm import trange, tqdm + from multiprocessing import Pool, RLock, freeze_support + + L = list(range(9)) + + def progresser(n): + interval = 0.001 / (n + 2) + total = 5000 + text = "#{}, est. {:<04.2}s".format(n, interval * total) + for _ in trange(total, desc=text, position=n): + sleep(interval) + + if __name__ == '__main__': + freeze_support() # for Windows support + tqdm.set_lock(RLock()) # for managing output contention + p = Pool(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),)) + p.map(progresser, L) + +Note that in Python 3, ``tqdm.write`` is thread-safe: + +.. code:: python + + from time import sleep + from tqdm import tqdm, trange + from concurrent.futures import ThreadPoolExecutor + + L = list(range(9)) + + def progresser(n): + interval = 0.001 / (n + 2) + total = 5000 + text = "#{}, est. {:<04.2}s".format(n, interval * total) + for _ in trange(total, desc=text): + sleep(interval) + if n == 6: + tqdm.write("n == 6 completed.") + tqdm.write("`tqdm.write()` is thread-safe in py3!") + + if __name__ == '__main__': + with ThreadPoolExecutor() as p: + p.map(progresser, L) + +Hooks and callbacks +~~~~~~~~~~~~~~~~~~~ + +``tqdm`` can easily support callbacks/hooks and manual updates. +Here's an example with ``urllib``: + +**``urllib.urlretrieve`` documentation** + + | [...] + | If present, the hook function will be called once + | on establishment of the network connection and once after each block read + | thereafter. The hook will be passed three arguments; a count of blocks + | transferred so far, a block size in bytes, and the total size of the file. + | [...] + +.. code:: python + + import urllib, os + from tqdm import tqdm + urllib = getattr(urllib, 'request', urllib) + + class TqdmUpTo(tqdm): + """Provides `update_to(n)` which uses `tqdm.update(delta_n)`.""" + def update_to(self, b=1, bsize=1, tsize=None): + """ + b : int, optional + Number of blocks transferred so far [default: 1]. + bsize : int, optional + Size of each block (in tqdm units) [default: 1]. + tsize : int, optional + Total size (in tqdm units). If [default: None] remains unchanged. + """ + if tsize is not None: + self.total = tsize + return self.update(b * bsize - self.n) # also sets self.n = b * bsize + + eg_link = "https://caspersci.uk.to/matryoshka.zip" + with TqdmUpTo(unit='B', unit_scale=True, unit_divisor=1024, miniters=1, + desc=eg_link.split('/')[-1]) as t: # all optional kwargs + urllib.urlretrieve(eg_link, filename=os.devnull, + reporthook=t.update_to, data=None) + t.total = t.n + +Inspired by `twine#242 `__. +Functional alternative in +`examples/tqdm_wget.py `__. + +It is recommend to use ``miniters=1`` whenever there is potentially +large differences in iteration speed (e.g. downloading a file over +a patchy connection). + +**Wrapping read/write methods** + +To measure throughput through a file-like object's ``read`` or ``write`` +methods, use ``CallbackIOWrapper``: + +.. code:: python + + from tqdm.auto import tqdm + from tqdm.utils import CallbackIOWrapper + + with tqdm(total=file_obj.size, + unit='B', unit_scale=True, unit_divisor=1024) as t: + fobj = CallbackIOWrapper(t.update, file_obj, "read") + while True: + chunk = fobj.read(chunk_size) + if not chunk: + break + t.reset() + # ... continue to use `t` for something else + +Alternatively, use the even simpler ``wrapattr`` convenience function, +which would condense both the ``urllib`` and ``CallbackIOWrapper`` examples +down to: + +.. code:: python + + import urllib, os + from tqdm import tqdm + + eg_link = "https://caspersci.uk.to/matryoshka.zip" + response = getattr(urllib, 'request', urllib).urlopen(eg_link) + with tqdm.wrapattr(open(os.devnull, "wb"), "write", + miniters=1, desc=eg_link.split('/')[-1], + total=getattr(response, 'length', None)) as fout: + for chunk in response: + fout.write(chunk) + +The ``requests`` equivalent is nearly identical: + +.. code:: python + + import requests, os + from tqdm import tqdm + + eg_link = "https://caspersci.uk.to/matryoshka.zip" + response = requests.get(eg_link, stream=True) + with tqdm.wrapattr(open(os.devnull, "wb"), "write", + miniters=1, desc=eg_link.split('/')[-1], + total=int(response.headers.get('content-length', 0))) as fout: + for chunk in response.iter_content(chunk_size=4096): + fout.write(chunk) + +**Custom callback** + +``tqdm`` is known for intelligently skipping unnecessary displays. To make a +custom callback take advantage of this, simply use the return value of +``update()``. This is set to ``True`` if a ``display()`` was triggered. + +.. code:: python + + from tqdm.auto import tqdm as std_tqdm + + def external_callback(*args, **kwargs): + ... + + class TqdmExt(std_tqdm): + def update(self, n=1): + displayed = super(TqdmExt, self).update(n) + if displayed: + external_callback(**self.format_dict) + return displayed + +``asyncio`` +~~~~~~~~~~~ + +Note that ``break`` isn't currently caught by asynchronous iterators. +This means that ``tqdm`` cannot clean up after itself in this case: + +.. code:: python + + from tqdm.asyncio import tqdm + + async for i in tqdm(range(9)): + if i == 2: + break + +Instead, either call ``pbar.close()`` manually or use the context manager syntax: + +.. code:: python + + from tqdm.asyncio import tqdm + + with tqdm(range(9)) as pbar: + async for i in pbar: + if i == 2: + break + +Pandas Integration +~~~~~~~~~~~~~~~~~~ + +Due to popular demand we've added support for ``pandas`` -- here's an example +for ``DataFrame.progress_apply`` and ``DataFrameGroupBy.progress_apply``: + +.. code:: python + + import pandas as pd + import numpy as np + from tqdm import tqdm + + df = pd.DataFrame(np.random.randint(0, 100, (100000, 6))) + + # Register `pandas.progress_apply` and `pandas.Series.map_apply` with `tqdm` + # (can use `tqdm.gui.tqdm`, `tqdm.notebook.tqdm`, optional kwargs, etc.) + tqdm.pandas(desc="my bar!") + + # Now you can use `progress_apply` instead of `apply` + # and `progress_map` instead of `map` + df.progress_apply(lambda x: x**2) + # can also groupby: + # df.groupby(0).progress_apply(lambda x: x**2) + +In case you're interested in how this works (and how to modify it for your +own callbacks), see the +`examples `__ +folder or import the module and run ``help()``. + +Keras Integration +~~~~~~~~~~~~~~~~~ + +A ``keras`` callback is also available: + +.. code:: python + + from tqdm.keras import TqdmCallback + + ... + + model.fit(..., verbose=0, callbacks=[TqdmCallback()]) + +Dask Integration +~~~~~~~~~~~~~~~~ + +A ``dask`` callback is also available: + +.. code:: python + + from tqdm.dask import TqdmCallback + + with TqdmCallback(desc="compute"): + ... + arr.compute() + + # or use callback globally + cb = TqdmCallback(desc="global") + cb.register() + arr.compute() + +IPython/Jupyter Integration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +IPython/Jupyter is supported via the ``tqdm.notebook`` submodule: + +.. code:: python + + from tqdm.notebook import trange, tqdm + from time import sleep + + for i in trange(3, desc='1st loop'): + for j in tqdm(range(100), desc='2nd loop'): + sleep(0.01) + +In addition to ``tqdm`` features, the submodule provides a native Jupyter +widget (compatible with IPython v1-v4 and Jupyter), fully working nested bars +and colour hints (blue: normal, green: completed, red: error/interrupt, +light blue: no ETA); as demonstrated below. + +|Screenshot-Jupyter1| +|Screenshot-Jupyter2| +|Screenshot-Jupyter3| + +The ``notebook`` version supports percentage or pixels for overall width +(e.g.: ``ncols='100%'`` or ``ncols='480px'``). + +It is also possible to let ``tqdm`` automatically choose between +console or notebook versions by using the ``autonotebook`` submodule: + +.. code:: python + + from tqdm.autonotebook import tqdm + tqdm.pandas() + +Note that this will issue a ``TqdmExperimentalWarning`` if run in a notebook +since it is not meant to be possible to distinguish between ``jupyter notebook`` +and ``jupyter console``. Use ``auto`` instead of ``autonotebook`` to suppress +this warning. + +Note that notebooks will display the bar in the cell where it was created. +This may be a different cell from the one where it is used. +If this is not desired, either + +- delay the creation of the bar to the cell where it must be displayed, or +- create the bar with ``display=False``, and in a later cell call + ``display(bar.container)``: + +.. code:: python + + from tqdm.notebook import tqdm + pbar = tqdm(..., display=False) + +.. code:: python + + # different cell + display(pbar.container) + +The ``keras`` callback has a ``display()`` method which can be used likewise: + +.. code:: python + + from tqdm.keras import TqdmCallback + cbk = TqdmCallback(display=False) + +.. code:: python + + # different cell + cbk.display() + model.fit(..., verbose=0, callbacks=[cbk]) + +Another possibility is to have a single bar (near the top of the notebook) +which is constantly re-used (using ``reset()`` rather than ``close()``). +For this reason, the notebook version (unlike the CLI version) does not +automatically call ``close()`` upon ``Exception``. + +.. code:: python + + from tqdm.notebook import tqdm + pbar = tqdm() + +.. code:: python + + # different cell + iterable = range(100) + pbar.reset(total=len(iterable)) # initialise with new `total` + for i in iterable: + pbar.update() + pbar.refresh() # force print final status but don't `close()` + +Custom Integration +~~~~~~~~~~~~~~~~~~ + +To change the default arguments (such as making ``dynamic_ncols=True``), +simply use built-in Python magic: + +.. code:: python + + from functools import partial + from tqdm import tqdm as std_tqdm + tqdm = partial(std_tqdm, dynamic_ncols=True) + +For further customisation, +``tqdm`` may be inherited from to create custom callbacks (as with the +``TqdmUpTo`` example `above <#hooks-and-callbacks>`__) or for custom frontends +(e.g. GUIs such as notebook or plotting packages). In the latter case: + +1. ``def __init__()`` to call ``super().__init__(..., gui=True)`` to disable + terminal ``status_printer`` creation. +2. Redefine: ``close()``, ``clear()``, ``display()``. + +Consider overloading ``display()`` to use e.g. +``self.frontend(**self.format_dict)`` instead of ``self.sp(repr(self))``. + +Some submodule examples of inheritance: + +- `tqdm/notebook.py `__ +- `tqdm/gui.py `__ +- `tqdm/tk.py `__ +- `tqdm/contrib/slack.py `__ +- `tqdm/contrib/discord.py `__ +- `tqdm/contrib/telegram.py `__ + +Dynamic Monitor/Meter +~~~~~~~~~~~~~~~~~~~~~ + +You can use a ``tqdm`` as a meter which is not monotonically increasing. +This could be because ``n`` decreases (e.g. a CPU usage monitor) or ``total`` +changes. + +One example would be recursively searching for files. The ``total`` is the +number of objects found so far, while ``n`` is the number of those objects which +are files (rather than folders): + +.. code:: python + + from tqdm import tqdm + import os.path + + def find_files_recursively(path, show_progress=True): + files = [] + # total=1 assumes `path` is a file + t = tqdm(total=1, unit="file", disable=not show_progress) + if not os.path.exists(path): + raise IOError("Cannot find:" + path) + + def append_found_file(f): + files.append(f) + t.update() + + def list_found_dir(path): + """returns os.listdir(path) assuming os.path.isdir(path)""" + listing = os.listdir(path) + # subtract 1 since a "file" we found was actually this directory + t.total += len(listing) - 1 + # fancy way to give info without forcing a refresh + t.set_postfix(dir=path[-10:], refresh=False) + t.update(0) # may trigger a refresh + return listing + + def recursively_search(path): + if os.path.isdir(path): + for f in list_found_dir(path): + recursively_search(os.path.join(path, f)) + else: + append_found_file(path) + + recursively_search(path) + t.set_postfix(dir=path) + t.close() + return files + +Using ``update(0)`` is a handy way to let ``tqdm`` decide when to trigger a +display refresh to avoid console spamming. + +Writing messages +~~~~~~~~~~~~~~~~ + +This is a work in progress (see +`#737 `__). + +Since ``tqdm`` uses a simple printing mechanism to display progress bars, +you should not write any message in the terminal using ``print()`` while +a progressbar is open. + +To write messages in the terminal without any collision with ``tqdm`` bar +display, a ``.write()`` method is provided: + +.. code:: python + + from tqdm.auto import tqdm, trange + from time import sleep + + bar = trange(10) + for i in bar: + # Print using tqdm class method .write() + sleep(0.1) + if not (i % 3): + tqdm.write("Done task %i" % i) + # Can also use bar.write() + +By default, this will print to standard output ``sys.stdout``. but you can +specify any file-like object using the ``file`` argument. For example, this +can be used to redirect the messages writing to a log file or class. + +Redirecting writing +~~~~~~~~~~~~~~~~~~~ + +If using a library that can print messages to the console, editing the library +by replacing ``print()`` with ``tqdm.write()`` may not be desirable. +In that case, redirecting ``sys.stdout`` to ``tqdm.write()`` is an option. + +To redirect ``sys.stdout``, create a file-like class that will write +any input string to ``tqdm.write()``, and supply the arguments +``file=sys.stdout, dynamic_ncols=True``. + +A reusable canonical example is given below: + +.. code:: python + + from time import sleep + import contextlib + import sys + from tqdm import tqdm + from tqdm.contrib import DummyTqdmFile + + + @contextlib.contextmanager + def std_out_err_redirect_tqdm(): + orig_out_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = map(DummyTqdmFile, orig_out_err) + yield orig_out_err[0] + # Relay exceptions + except Exception as exc: + raise exc + # Always restore sys.stdout/err if necessary + finally: + sys.stdout, sys.stderr = orig_out_err + + def some_fun(i): + print("Fee, fi, fo,".split()[i]) + + # Redirect stdout to tqdm.write() (don't forget the `as save_stdout`) + with std_out_err_redirect_tqdm() as orig_stdout: + # tqdm needs the original stdout + # and dynamic_ncols=True to autodetect console width + for i in tqdm(range(3), file=orig_stdout, dynamic_ncols=True): + sleep(.5) + some_fun(i) + + # After the `with`, printing is restored + print("Done!") + +Redirecting ``logging`` +~~~~~~~~~~~~~~~~~~~~~~~ + +Similar to ``sys.stdout``/``sys.stderr`` as detailed above, console ``logging`` +may also be redirected to ``tqdm.write()``. + +Warning: if also redirecting ``sys.stdout``/``sys.stderr``, make sure to +redirect ``logging`` first if needed. + +Helper methods are available in ``tqdm.contrib.logging``. For example: + +.. code:: python + + import logging + from tqdm import trange + from tqdm.contrib.logging import logging_redirect_tqdm + + LOG = logging.getLogger(__name__) + + if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + with logging_redirect_tqdm(): + for i in trange(9): + if i == 4: + LOG.info("console logging redirected to `tqdm.write()`") + # logging restored + +Monitoring thread, intervals and miniters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``tqdm`` implements a few tricks to increase efficiency and reduce overhead. + +- Avoid unnecessary frequent bar refreshing: ``mininterval`` defines how long + to wait between each refresh. ``tqdm`` always gets updated in the background, + but it will display only every ``mininterval``. +- Reduce number of calls to check system clock/time. +- ``mininterval`` is more intuitive to configure than ``miniters``. + A clever adjustment system ``dynamic_miniters`` will automatically adjust + ``miniters`` to the amount of iterations that fit into time ``mininterval``. + Essentially, ``tqdm`` will check if it's time to print without actually + checking time. This behaviour can be still be bypassed by manually setting + ``miniters``. + +However, consider a case with a combination of fast and slow iterations. +After a few fast iterations, ``dynamic_miniters`` will set ``miniters`` to a +large number. When iteration rate subsequently slows, ``miniters`` will +remain large and thus reduce display update frequency. To address this: + +- ``maxinterval`` defines the maximum time between display refreshes. + A concurrent monitoring thread checks for overdue updates and forces one + where necessary. + +The monitoring thread should not have a noticeable overhead, and guarantees +updates at least every 10 seconds by default. +This value can be directly changed by setting the ``monitor_interval`` of +any ``tqdm`` instance (i.e. ``t = tqdm.tqdm(...); t.monitor_interval = 2``). +The monitor thread may be disabled application-wide by setting +``tqdm.tqdm.monitor_interval = 0`` before instantiation of any ``tqdm`` bar. + + +Merch +----- + +You can buy `tqdm branded merch `__ now! + +Contributions +------------- + +|GitHub-Commits| |GitHub-Issues| |GitHub-PRs| |OpenHub-Status| |GitHub-Contributions| |CII Best Practices| + +All source code is hosted on `GitHub `__. +Contributions are welcome. + +See the +`CONTRIBUTING `__ +file for more information. + +Developers who have made significant contributions, ranked by *SLoC* +(surviving lines of code, +`git fame `__ ``-wMC --excl '\.(png|gif|jpg)$'``), +are: + +==================== ======================================================== ==== ================================ +Name ID SLoC Notes +==================== ======================================================== ==== ================================ +Casper da Costa-Luis `casperdcl `__ ~78% primary maintainer |Gift-Casper| +Stephen Larroque `lrq3000 `__ ~10% team member +Martin Zugnoni `martinzugnoni `__ ~4% +Daniel Ecer `de-code `__ ~2% +Richard Sheridan `richardsheridan `__ ~1% +Guangshuo Chen `chengs `__ ~1% +Kyle Altendorf `altendky `__ <1% +Matthew Stevens `mjstevens777 `__ <1% +Hadrien Mary `hadim `__ <1% team member +Noam Yorav-Raphael `noamraph `__ <1% original author +Mikhail Korobov `kmike `__ <1% team member +==================== ======================================================== ==== ================================ + +Ports to Other Languages +~~~~~~~~~~~~~~~~~~~~~~~~ + +A list is available on +`this wiki page `__. + + +LICENCE +------- + +Open Source (OSI approved): |LICENCE| + +Citation information: |DOI| + +|README-Hits| (Since 19 May 2016) + +.. |Logo| image:: https://img.tqdm.ml/logo.gif +.. |Screenshot| image:: https://img.tqdm.ml/tqdm.gif +.. |Video| image:: https://img.tqdm.ml/video.jpg + :target: https://tqdm.github.io/video +.. |Slides| image:: https://img.tqdm.ml/slides.jpg + :target: https://tqdm.github.io/PyData2019/slides.html +.. |Merch| image:: https://img.tqdm.ml/merch.jpg + :target: https://tqdm.github.io/merch +.. |Build-Status| image:: https://img.shields.io/github/actions/workflow/status/tqdm/tqdm/test.yml?branch=master&label=tqdm&logo=GitHub + :target: https://github.com/tqdm/tqdm/actions/workflows/test.yml +.. |Coverage-Status| image:: https://img.shields.io/coveralls/github/tqdm/tqdm/master?logo=coveralls + :target: https://coveralls.io/github/tqdm/tqdm +.. |Branch-Coverage-Status| image:: https://codecov.io/gh/tqdm/tqdm/branch/master/graph/badge.svg + :target: https://codecov.io/gh/tqdm/tqdm +.. |Codacy-Grade| image:: https://app.codacy.com/project/badge/Grade/3f965571598f44549c7818f29cdcf177 + :target: https://www.codacy.com/gh/tqdm/tqdm/dashboard +.. |CII Best Practices| image:: https://bestpractices.coreinfrastructure.org/projects/3264/badge + :target: https://bestpractices.coreinfrastructure.org/projects/3264 +.. |GitHub-Status| image:: https://img.shields.io/github/tag/tqdm/tqdm.svg?maxAge=86400&logo=github&logoColor=white + :target: https://github.com/tqdm/tqdm/releases +.. |GitHub-Forks| image:: https://img.shields.io/github/forks/tqdm/tqdm.svg?logo=github&logoColor=white + :target: https://github.com/tqdm/tqdm/network +.. |GitHub-Stars| image:: https://img.shields.io/github/stars/tqdm/tqdm.svg?logo=github&logoColor=white + :target: https://github.com/tqdm/tqdm/stargazers +.. |GitHub-Commits| image:: https://img.shields.io/github/commit-activity/y/tqdm/tqdm.svg?logo=git&logoColor=white + :target: https://github.com/tqdm/tqdm/graphs/commit-activity +.. |GitHub-Issues| image:: https://img.shields.io/github/issues-closed/tqdm/tqdm.svg?logo=github&logoColor=white + :target: https://github.com/tqdm/tqdm/issues?q= +.. |GitHub-PRs| image:: https://img.shields.io/github/issues-pr-closed/tqdm/tqdm.svg?logo=github&logoColor=white + :target: https://github.com/tqdm/tqdm/pulls +.. |GitHub-Contributions| image:: https://img.shields.io/github/contributors/tqdm/tqdm.svg?logo=github&logoColor=white + :target: https://github.com/tqdm/tqdm/graphs/contributors +.. |GitHub-Updated| image:: https://img.shields.io/github/last-commit/tqdm/tqdm/master.svg?logo=github&logoColor=white&label=pushed + :target: https://github.com/tqdm/tqdm/pulse +.. |Gift-Casper| image:: https://img.shields.io/badge/dynamic/json.svg?color=ff69b4&label=gifts%20received&prefix=%C2%A3&query=%24..sum&url=https%3A%2F%2Fcaspersci.uk.to%2Fgifts.json + :target: https://cdcl.ml/sponsor +.. |Versions| image:: https://img.shields.io/pypi/v/tqdm.svg + :target: https://tqdm.github.io/releases +.. |PyPI-Downloads| image:: https://img.shields.io/pypi/dm/tqdm.svg?label=pypi%20downloads&logo=PyPI&logoColor=white + :target: https://pepy.tech/project/tqdm +.. |Py-Versions| image:: https://img.shields.io/pypi/pyversions/tqdm.svg?logo=python&logoColor=white + :target: https://pypi.org/project/tqdm +.. |Conda-Forge-Status| image:: https://img.shields.io/conda/v/conda-forge/tqdm.svg?label=conda-forge&logo=conda-forge + :target: https://anaconda.org/conda-forge/tqdm +.. |Snapcraft| image:: https://img.shields.io/badge/snap-install-82BEA0.svg?logo=snapcraft + :target: https://snapcraft.io/tqdm +.. |Docker| image:: https://img.shields.io/badge/docker-pull-blue.svg?logo=docker&logoColor=white + :target: https://hub.docker.com/r/tqdm/tqdm +.. |Libraries-Rank| image:: https://img.shields.io/librariesio/sourcerank/pypi/tqdm.svg?logo=koding&logoColor=white + :target: https://libraries.io/pypi/tqdm +.. |Libraries-Dependents| image:: https://img.shields.io/librariesio/dependent-repos/pypi/tqdm.svg?logo=koding&logoColor=white + :target: https://github.com/tqdm/tqdm/network/dependents +.. |OpenHub-Status| image:: https://www.openhub.net/p/tqdm/widgets/project_thin_badge?format=gif + :target: https://www.openhub.net/p/tqdm?ref=Thin+badge +.. |awesome-python| image:: https://awesome.re/mentioned-badge.svg + :target: https://github.com/vinta/awesome-python +.. |LICENCE| image:: https://img.shields.io/pypi/l/tqdm.svg + :target: https://raw.githubusercontent.com/tqdm/tqdm/master/LICENCE +.. |DOI| image:: https://img.shields.io/badge/DOI-10.5281/zenodo.595120-blue.svg + :target: https://doi.org/10.5281/zenodo.595120 +.. |binder-demo| image:: https://mybinder.org/badge_logo.svg + :target: https://mybinder.org/v2/gh/tqdm/tqdm/master?filepath=DEMO.ipynb +.. |Screenshot-Jupyter1| image:: https://img.tqdm.ml/jupyter-1.gif +.. |Screenshot-Jupyter2| image:: https://img.tqdm.ml/jupyter-2.gif +.. |Screenshot-Jupyter3| image:: https://img.tqdm.ml/jupyter-3.gif +.. |README-Hits| image:: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&style=social&r=https://github.com/tqdm/tqdm&l=https://img.tqdm.ml/favicon.png&f=https://img.tqdm.ml/logo.gif + :target: https://caspersci.uk.to/cgi-bin/hits.cgi?q=tqdm&a=plot&r=https://github.com/tqdm/tqdm&l=https://img.tqdm.ml/favicon.png&f=https://img.tqdm.ml/logo.gif&style=social diff --git a/lib/tqdm-4.65.0.dist-info/RECORD b/lib/tqdm-4.65.0.dist-info/RECORD new file mode 100644 index 0000000..b019bcb --- /dev/null +++ b/lib/tqdm-4.65.0.dist-info/RECORD @@ -0,0 +1,74 @@ +../../Scripts/tqdm.exe,sha256=dcwBwqZZOjA0_EX7BlHbNv6grTaPie823mt6MS4sD2k,106355 +tqdm-4.65.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +tqdm-4.65.0.dist-info/LICENCE,sha256=gX2ENlOwRSP4vIrRcbwwJIkZREIZO_FKb3VNZDO3SUE,2006 +tqdm-4.65.0.dist-info/METADATA,sha256=TKT91OQsgMt-V15butbdqttdFLXjQgSh1ciFnJcYO7o,56978 +tqdm-4.65.0.dist-info/RECORD,, +tqdm-4.65.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 +tqdm-4.65.0.dist-info/entry_points.txt,sha256=ReJCH7Ui3Zyh6M16E4OhsZ1oU7WtMXCfbtoyBhGO29Y,39 +tqdm-4.65.0.dist-info/top_level.txt,sha256=NLiUJNfmc9At15s7JURiwvqMEjUi9G5PMGRrmMYzNSM,5 +tqdm/__init__.py,sha256=9mQNYSSqP99JasubEC1POJLMmhkkBH6cJZxPIR5G2pQ,1572 +tqdm/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 +tqdm/__pycache__/__init__.cpython-39.pyc,, +tqdm/__pycache__/__main__.cpython-39.pyc,, +tqdm/__pycache__/_dist_ver.cpython-39.pyc,, +tqdm/__pycache__/_main.cpython-39.pyc,, +tqdm/__pycache__/_monitor.cpython-39.pyc,, +tqdm/__pycache__/_tqdm.cpython-39.pyc,, +tqdm/__pycache__/_tqdm_gui.cpython-39.pyc,, +tqdm/__pycache__/_tqdm_notebook.cpython-39.pyc,, +tqdm/__pycache__/_tqdm_pandas.cpython-39.pyc,, +tqdm/__pycache__/_utils.cpython-39.pyc,, +tqdm/__pycache__/asyncio.cpython-39.pyc,, +tqdm/__pycache__/auto.cpython-39.pyc,, +tqdm/__pycache__/autonotebook.cpython-39.pyc,, +tqdm/__pycache__/cli.cpython-39.pyc,, +tqdm/__pycache__/dask.cpython-39.pyc,, +tqdm/__pycache__/gui.cpython-39.pyc,, +tqdm/__pycache__/keras.cpython-39.pyc,, +tqdm/__pycache__/notebook.cpython-39.pyc,, +tqdm/__pycache__/rich.cpython-39.pyc,, +tqdm/__pycache__/std.cpython-39.pyc,, +tqdm/__pycache__/tk.cpython-39.pyc,, +tqdm/__pycache__/utils.cpython-39.pyc,, +tqdm/__pycache__/version.cpython-39.pyc,, +tqdm/_dist_ver.py,sha256=JOO-LUfimc0udCkOSSs7hkXREdCEsxiiJQsYgAnCYQ0,23 +tqdm/_main.py,sha256=9ySvgmi_2Sw4CAo5UDW0Q2dxfTryboEWGHohfCJz0sA,283 +tqdm/_monitor.py,sha256=Uku-DPWgzJ7dO5CK08xKJK-E_F6qQ-JB3ksuXczSYR0,3699 +tqdm/_tqdm.py,sha256=LfLCuJ6bpsVo9xilmtBXyEm1vGnUCFrliW85j3J-nD4,283 +tqdm/_tqdm_gui.py,sha256=03Hc8KayxJveieI5-0-2NGiDpLvw9jZekofJUV7CCwk,287 +tqdm/_tqdm_notebook.py,sha256=BuHiLuxu6uEfZFaPJW3RPpPaxaVctEQA3kdSJSDL1hw,307 +tqdm/_tqdm_pandas.py,sha256=c9jptUgigN6axRDhRd4Rif98Tmxeopc1nFNFhIpbFUE,888 +tqdm/_utils.py,sha256=_4E73bfDj4f1s3sM42NLHNrZDOkijZoWq-n6xWLkdZ8,553 +tqdm/asyncio.py,sha256=WcWbVEjc1-GxqnN0BVntDuwYR31JN9SV7ERZhEz8kKo,2775 +tqdm/auto.py,sha256=nDZflj6p2zKkjBCNBourrhS81zYfZy1_dQvbckrdW8o,871 +tqdm/autonotebook.py,sha256=Yb9F5uaiBPhfbDDFpbtoG8I2YUw3uQJ89rUDLbfR6ws,956 +tqdm/cli.py,sha256=f_ScGw6GYv35VwVzJ9T-AAknqV0vNHQQ4YEdWgjvi2E,10727 +tqdm/completion.sh,sha256=j79KbSmpIj_E11jfTfBXrGnUTzKXVpQ1vGVQvsyDRl4,946 +tqdm/contrib/__init__.py,sha256=cNNaRURdcPjbQpkxPGe4iaShyQr_Dx8h6NQJubPhq7g,2513 +tqdm/contrib/__pycache__/__init__.cpython-39.pyc,, +tqdm/contrib/__pycache__/bells.cpython-39.pyc,, +tqdm/contrib/__pycache__/concurrent.cpython-39.pyc,, +tqdm/contrib/__pycache__/discord.cpython-39.pyc,, +tqdm/contrib/__pycache__/itertools.cpython-39.pyc,, +tqdm/contrib/__pycache__/logging.cpython-39.pyc,, +tqdm/contrib/__pycache__/slack.cpython-39.pyc,, +tqdm/contrib/__pycache__/telegram.cpython-39.pyc,, +tqdm/contrib/__pycache__/utils_worker.cpython-39.pyc,, +tqdm/contrib/bells.py,sha256=Yx1HqGCmHrESCAO700j5wE__JCleNODJxedh1ijPLD0,837 +tqdm/contrib/concurrent.py,sha256=K1yjloKS5WRNFyjLRth0DmU5PAnDbF0A-GD27N-J4a8,3986 +tqdm/contrib/discord.py,sha256=8dX7OyKmFC-sT8BcfqTCmBLBRI9237xBvAHBqe_4AqA,3955 +tqdm/contrib/itertools.py,sha256=WdKKQU5eSzsqHu29SN_oH12huYZo0Jihqoi9-nVhwz4,774 +tqdm/contrib/logging.py,sha256=Ix1p4j9zI1YVcr7UgHHhpFFug4ed0nHUSRnBUXzlKaA,3804 +tqdm/contrib/slack.py,sha256=AdULxp_CDZJ1thzgrs_0JbJs2dkca8a5-Oimhkqh32s,4061 +tqdm/contrib/telegram.py,sha256=O7HLYTxVsX3PTJv10lJDPzfRMq7U7IJ_q9BpsVo7byY,5093 +tqdm/contrib/utils_worker.py,sha256=HJP5Mz1S1xyzEke2JaqJ2sYLHXADYoo2epT5AzQ38eA,1207 +tqdm/dask.py,sha256=RWR3rz9s36zNceeESTm-F3H1F7dxuBuqS1bhifOuvEY,1337 +tqdm/gui.py,sha256=scSpal_3ZMAZ1molN5F8_KUWyY09HsTbOHQy4LwPZBs,5800 +tqdm/keras.py,sha256=crCBtNnOr8ZzdQVPzPNWXDH5dMnsmpfkFM289FzO5Ds,4359 +tqdm/notebook.py,sha256=7OvvOzJn2izMm23MznkHjK9M4GOoWcbvGysjVAMVvkU,11049 +tqdm/rich.py,sha256=8SamiW-LlUKEMQwzubomHCXkX6awaz-7Tq-gnMkJJ5A,5018 +tqdm/std.py,sha256=tqM_QE6zcjovc9OT95YAH6SvYkVjiugK6nGgBNYKdVI,57678 +tqdm/tk.py,sha256=fmMdC2hZQCQpfB1En4YGISHEhAqZdo-qYnD0ivO6C0Q,6718 +tqdm/tqdm.1,sha256=1YMLZFiY0wGAUYgjmrfr9vQTlyMql6LT31oUWvOyQdU,7997 +tqdm/utils.py,sha256=fkozOR_ONclRNkPduqkT9nsQf1pVzIMExb8sLD2c3dU,9510 +tqdm/version.py,sha256=-1yWjfu3P0eghVsysHH07fbzdiADNRdzRtYPqOaqR2A,333 diff --git a/lib/tqdm-4.65.0.dist-info/WHEEL b/lib/tqdm-4.65.0.dist-info/WHEEL new file mode 100644 index 0000000..57e3d84 --- /dev/null +++ b/lib/tqdm-4.65.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lib/tqdm-4.65.0.dist-info/entry_points.txt b/lib/tqdm-4.65.0.dist-info/entry_points.txt new file mode 100644 index 0000000..540e60f --- /dev/null +++ b/lib/tqdm-4.65.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +tqdm = tqdm.cli:main diff --git a/lib/tqdm-4.65.0.dist-info/top_level.txt b/lib/tqdm-4.65.0.dist-info/top_level.txt new file mode 100644 index 0000000..78620c4 --- /dev/null +++ b/lib/tqdm-4.65.0.dist-info/top_level.txt @@ -0,0 +1 @@ +tqdm diff --git a/lib/tqdm/__init__.py b/lib/tqdm/__init__.py new file mode 100644 index 0000000..8081f77 --- /dev/null +++ b/lib/tqdm/__init__.py @@ -0,0 +1,38 @@ +from ._monitor import TMonitor, TqdmSynchronisationWarning +from ._tqdm_pandas import tqdm_pandas +from .cli import main # TODO: remove in v5.0.0 +from .gui import tqdm as tqdm_gui # TODO: remove in v5.0.0 +from .gui import trange as tgrange # TODO: remove in v5.0.0 +from .std import ( + TqdmDeprecationWarning, TqdmExperimentalWarning, TqdmKeyError, TqdmMonitorWarning, + TqdmTypeError, TqdmWarning, tqdm, trange) +from .version import __version__ + +__all__ = ['tqdm', 'tqdm_gui', 'trange', 'tgrange', 'tqdm_pandas', + 'tqdm_notebook', 'tnrange', 'main', 'TMonitor', + 'TqdmTypeError', 'TqdmKeyError', + 'TqdmWarning', 'TqdmDeprecationWarning', + 'TqdmExperimentalWarning', + 'TqdmMonitorWarning', 'TqdmSynchronisationWarning', + '__version__'] + + +def tqdm_notebook(*args, **kwargs): # pragma: no cover + """See tqdm.notebook.tqdm for full documentation""" + from warnings import warn + + from .notebook import tqdm as _tqdm_notebook + warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`", + TqdmDeprecationWarning, stacklevel=2) + return _tqdm_notebook(*args, **kwargs) + + +def tnrange(*args, **kwargs): # pragma: no cover + """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" + from warnings import warn + + from .notebook import trange as _tnrange + warn("Please use `tqdm.notebook.trange` instead of `tqdm.tnrange`", + TqdmDeprecationWarning, stacklevel=2) + return _tnrange(*args, **kwargs) diff --git a/lib/tqdm/__main__.py b/lib/tqdm/__main__.py new file mode 100644 index 0000000..4e28416 --- /dev/null +++ b/lib/tqdm/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/lib/tqdm/__pycache__/__init__.cpython-39.pyc b/lib/tqdm/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2e899b2a5467a8efdc0f82b205ea0fa5a913b97 GIT binary patch literal 1534 zcmcIkOK;pZ5Y}t8@~(FMh+`*x>d=E_G@GDkFKJU0XaW>{1M8pwu>e;V>9t@|Qb?{- z8_uoQ7X1xHFFEuN@!FIBLJw_csC8kZL2s47(F|ua{5T(n#b`9};Q8#gyX>9V^L|I; z`V)Zh2@L%+2<9UIz1Mt9FdV6 zld)VU>vDr^$W5{-x5yU6ClEJqaeZ-1Zj)`_TX-z_#V5Df8XNrJksUT>ZBesAgBi)9s$aqv zr{NnLRn!ZfCw2$ZJm#(9<@FSH7w4aR-*Qz+UYkO!a@!OgU+}XhN&$*3v{sO=B1SlO zaMtoJWQgXCe!w;M0 z1e|r}>|s(QP>oTfDArLRaKA1e1JQ#c{c%gZ= zgfaj3(sKych8HYrj#mB~6`WsU(E!9io1#1s{FIBlXA^`3$4IMPaNFc+I~{pg(>$_K zp%&W4ClI>|Bx8Cb`%6!~4Y7ZG{^`TnQw>DVrlqRd*<-FxOw-O5t)cq3U|h`%*XF(1 zm(}4+SBBqf0Zg&rdWQM#Q(D1^r*zVusaue68-_-Xy&w%zKMJBhc`Fx^o14y-l!V#Q+cK+diaON_-inZ>b&($5wbr(e5 zQ?H|r45>FzU{kpRpGWFV)ZapZev;6uPC-H(D=I?`ONmq0%jrjd#ewsDo!_wumEC>Q z^%r&!Njsjq%cbkguoRWTCbA*+uIutmFgsLR_*bY6yVj7dNnKaEp2c>x{nd5gdertQ nErg)dQR@J8$Zzh!CLhU$Ed~D=ORRCJ4L@DIdtn+RYsv6$O2CL{ literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/__main__.cpython-39.pyc b/lib/tqdm/__pycache__/__main__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4d307c2d684c911ff1eff06118107ffde0b0253 GIT binary patch literal 205 zcmYe~<>g`k0*|$lDRMyiF^Gcg`k0*|$lDdIr-F^Gc<7=auIATH(r5-AK(3@MDk44O<;Y$keUrg{c`nvA!& z?m%-GapA?8Nlc;+T@clwAGzl+5B1klA_#mA5!-a`RJ4b5iX< JF8U0_3;?${G1UM7 literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/_main.cpython-39.pyc b/lib/tqdm/__pycache__/_main.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6bdb86b8f92072fa73e5eade4d5c70cebadbb71 GIT binary patch literal 439 zcmYjN%}N6?5Ki`|t!sM~#LHe=a4mutu_9Q|gNV?IP!_l8X4-~ke@xOAeFq=JgDvvY zc-DtfLCTCM@+X`X`Rwg^I2SscF7wo~;aW%;P9Ri}6)WJO$U}njMI zhN~K8A4KhA*uwYC^?CfLLFssqDpAIL&yvAmr iW2^FHr>1d3Xi@ literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/_monitor.cpython-39.pyc b/lib/tqdm/__pycache__/_monitor.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43dda3804075161305d7dca89029deca1a208643 GIT binary patch literal 2792 zcmZuz&u<*J6(%`9c6PLq9XH4?w8a!C&}>jkanS&QVHgJDqD9cEMWrCHk#rzeOWRh1brLU;?RHS0+rQXy}eNJOu6-MF=BFCHuaa7Qr5$QgkOI+ zI*(AyZB&Y-T(VS1o_bP18Ba$TUW_aRCewLk%e(8{_wLSgQz&B+rFElCaylxDQ8LZX zk|LiQm6UbSK;6P7wK5hv#|fUJxlI~7Qu?&SoVrPJ`9x_n)$uXrn*F}tMp;(pQ65^NCy9H1HUFLP3()CXuC?Rr#4)o!2sDOHOaJu z-M}r$=eC(5T#CG^&PZ~t$j7-p+l}42Kjj*(p}DSm_v&$7QZXwklO*?(qRP#B68YUJ z?}kVdijNPAyiSf(f+&G@6kf0~Pt5#i?ehNGOj9W}%gP#CKFKQ*d|WlTb=#5P!%O4b zJjk=BO4Z4wBMF9Lq>9NUW`zfwHxISRxu2n!0V>Nb_>!$S@)h4qNP*g0QrllrJ6N)D zc+rubjaJm}UiN((uf#dS$d>J0Y-4nXq%7IRtra6CfRBUyLrtls1F9%?`nNTyK;cgC zBg8|QD`?&Zr_DqmpEq!}J2z2igN)+1^@hqic{BhVoY*!#%}q8bt4hf>%&q#Sv~8r- zur$E1^<6(OHrEyu;~P~S>pP^=kEpszCJiEF`WC96q8OUNc+A7g_cp)7dwlWMhH`c{ zMigY^=Aky3>LC6(8h{2M6DUh&0ok1E9|I zk-hyJ%g7?~;sJ`GO@~>a9R_uP< z^71tc+W0{QFBQ#n_V6AFcibc5my3_CjDX7#E|6IxF?SzS4N}!SL}RlMlugb`<5$yK zzi2>dKg00rJ8s{Qw?~_jc}y(cdGnSxkZ}58wga$51HNP{_LRG%LE?TPM`g8+Rq2`1*V7 z<&ER_80xJ_pub1*dPq%g+#?eH4vF}!ck($UFiD?y`d@Zi`gVs7$OtQ+7yS^V)pS@26<50y+zxloCmYvLRtQ+Mbl9~z~S zk+m~(e{XMC+R^-I7yjL&)LvJmF>_`1e*Nja{tHJ7yp7@BVp6Fms_Ng;6qx!gs$5Rs ztS_xg);26nMj7J#9g!zVE&U73O`{E`r#%p{&1O&xH-~g8n-Gv?$cGo94qQkMYqkcziYXXnx28W-5thYmx|z12*&ma%Mc=^(@!$*f z>Zz~bL7cQ69GEXN-%MschBcc3LA(7t;>VPbFPr?g8YWlhx{tsKr;_9}rj&4pyV4oi z*ON}}#_o=*VeG{oB|v!{Jl^2`yR94yNh{dm4AP<-Z_QM(#dS8JZG=XeGAYv(!^6=F z&j(N`$e0mj@r1pin7uuZ=0Zo)WszAnS_>(o2}BC=as@mRMP%?}uXo<*cDliX1g0Tc zqK_@#(S~>0V{Fp~7>~;7AD`~3?eW&x%L%k$*+Rk!a(jh|V_%x)U<2l~XFKn7{9vWIl#9n+<|?{eH-gC?OxV`ENcpm*~2WffG(8$!JU|;SP7DGqTx} zPUgn$59UiR^JAY9pu7e?uk+y5GJ8YPYHaZj(xR2OVJhF^I@{1ThI*PZDbp0ogV7Vu z22d)PG9!xo5obj{f0>LHLPxVzKDB7H5mH7Ih!kYS8h9k~$l$|X@2u19bQ|{)n1*PD zKDKygz7n1G7{{~$#-n2Pi>H=NckI^LNor`rrb`KHkaw+G631~<|$8)@|Z(pH%mH+?% literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/_tqdm_notebook.cpython-39.pyc b/lib/tqdm/__pycache__/_tqdm_notebook.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1660f80a2a1ead3f3dc05e50de963e21f836ed07 GIT binary patch literal 467 zcmZ8d%}PQ+6rQ<1db8^`Xm_^?nG|gzCCEjK2tgx|f$QiT_2S&0W=5snp$BQv3*Bui zU!g^G#tNhZ=R1Gjne#EcUat|f+xKH}P6_$2#lHDiT%+qQf*^uQlF|{SL^#5g&d^3r zI;lHycRgQvsXy{50V*osi>e4-?c`uU8nrFHK^nC5He6*}JZB5qM5x9wmoknqJsLiV zv%iwQA z*>4)T+6Yy_?2V{H3_JMj-(0c>4N9}YR3!!LfnJ)tVDrMndcg%KWl^G@+i*%{H5iGqct$>uEiB zsh^<*FCP3tbM@p`@SwiQu28)C;62_#riQX~(0&$P~OYgkHI~Q1`3&I1q#peO< z^4TzyO*WPu56>WWfhOSym1u##;LjMN`W8fNx;&H3f3e0}pe3G!P@{haSl)wy z2B*_70(mKlQX8RkWX2*Y%6FoUhHji;RMy(CIv3Fi%-n*9s}W&(C6YolGkG*m_L*Ui z6`P7D?qYAANYxZdXib@v)qI$=p~}$`W4rwda_*ln$ha(n*SXad^>6}iSrn)!Chn)ctFGMIZCL8R3|imJrLf&X7-o#JArPyKcDz~-F0H5M z+6LNip_FnDCJk1zS~8Ncmj))UnAYj)p&g$vRp_?LSERN8{`26`z3jDyHD-soDrL4W z^s#AVR!Bqjm_gpm2yO0W&s0~dEB%a2(hbmltVr7h*_^~$+&|Ay;B-vEO;eX@uiQ`vYjn4k7>m literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/_utils.cpython-39.pyc b/lib/tqdm/__pycache__/_utils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e988d583ece725eabe8ab7021ad9c4c3de36bb43 GIT binary patch literal 820 zcmZXS%}yIJ5P)~{pO64a2vU36YbrHGm3pa4)e2e>35AM)sH~NYSr1@g@4B`(p?!xw zNDqA>Uwh&edZ_Aw9Exhm-_GB9#-3rlUMC9c;O7%Qu0+u<% z8mJU`dFuy{jZT*?ocSnlDRN#c?SJM~sy?-))`f8)OD?j4hr+1?qo^ttKuhggp|rv< z#lEqVYhjbiGSBc?lBJL`xq!p~hVn$@i3=~cyZdUgGui2UlE5q^W!UFvpOj8WJ9%~< z0<8nalj3sa2^V~D-GxcPesrv3*^Pt+$k}+?x3E2-1=gUSRITHt79?N4|Iy$qnFgYX zXd&7N96_^&=puTEb;Jf@6VXQu5JSWkVifQrGEY%P{65%wot|1SHl1fi=yV_KjVp9| zr5*NzgGn7&_cA>c7pWBvp6gIQy8@fy)nBYGsK5Hw8&&X9!do0Ri}iIY-T1IxtJMDd E1ss#}b^rhX literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/asyncio.cpython-39.pyc b/lib/tqdm/__pycache__/asyncio.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0bc4f5dd7dc959a2e161af5099e7214419e4621 GIT binary patch literal 3338 zcmaJ@&2QYs6`vuwTrRaM%ZlsPLD8~FftZfGR?-ikhAY%f?AmFeqE&*R#1<6I;jSog z$@L7kjuzA`SU3Y71%NxpK?3GW&xF3-PY2l^gNg+$G)O&4co|e@7#t1LX z=lq**ZTffJe!uV04`25TSg~!N*WKSGQl&*6runcq+ z*>|^xGEH)k9eVTQd&RK9SBq}P#wF9~Sk$hdDG5@tl2`25*f%OZ;+63Q|20clW&VYK z$`M}Rg=U5GO}WfV3q9l5kPj=q-vY-LCh_x|nqz?zfYT;UeywyWeoX5-72EGLvx4Cy zt?h?(Ck&%J`KS!T_iHB%o38KHEj5K_^igR(%~Ocf>pBRZWJwr3i_$VeuWs)}Dk@7U z9h{=>EhK0w*`Ya$=7OsWj_x3=;dDzS*|2ubS+y0(k*e*3XY@VgWL}&_N`>Jm``6=l z?hZas2(7_REYr#0-9#Of#bhv=l%d*-5aNR}QRUl%U!+e43VYt3MDYRQT@5IKX{+-{LlRxQj;AHC#UX+U0QUpCb@cFrWXZPwgEvMb4~%>M7rr*U|U6 zT*F8A8$$Xt-gMe9{4Rz{hXkKpe?b|rC6OgwSu;L%WE{m1s;d>lo%&_Mcamv=m{5LHBCV2LtSmu4qz%e$l>kf24 zo!^#SeB>%fZBHj6DwDdm0|z|Tl?nM?x8kf&Nxe?9IyEj>y`M%ZdGJx3OiD=lhDN70 zQR`lSNXO9|P@+gH;|6@QW{_uW-qju=`rnW>p}Pc`iS^`{U!tjX5CUwUR~(6DP-2}Q z&5&2YrD)8BZ#Ir2dHD(va_5B$rHA1U(axQBb;)_O#i?0aSy4<-#nN$7Ov}1e7G;zz zDgYVQMx#n{KwAw!DogMwlWQeBcFd2glpWb6T{yyq7u|T#{>Ja3AqRsTJ4q#8%AKBuf zZ~(k@%->}I-^P_;olv|0+94taz?;JU%xs_~86;dA35P3%Buk@eH){cQXLe@+;7h?C zMde;X_i+QV=Pe=w)-i&TO#VBXqI9be5lwy%@o3Q5sN>N$YM$17_(Fu%tIy!!ixT~F z+J`n}eD>xNT8$n3dsz`jncC_vuUp_pOUWP#SIP+WD#!?C{dRp|9PuOL4=}L8h4g3R zsRaWU3FDi!(chGPh|4=5r(AAQUkm7Z=-$gxRZ`?_ZG-bqG&XzV{~CF*V#z={ASJs- zjTC4DS5XJ&*v*Zjsxn7UzD0d9?80P8X36h?P}Pv%C-NPT?ImS|1}WP?)1j%AbsIFQ zm*AjP)jVvlSO9dP?N``IYXfGTB%hAywUVKo%cc?%1m{oTJ`HZ_7wFAf^cL|qyj!=LI_1yv7Izp~ShutoM*s81up#z7 z*`$-uk~H?a^2hi{AEY+bq0$vJF!5B^Z{EN`FC;Sv_-G3F@FJ)Ib)*3!)p{dIRqYaf h8cFSS=U(HlEqbKr`_Jk)ylZsvzH!X8`;KGr{{Uvr0F(d# literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/auto.cpython-39.pyc b/lib/tqdm/__pycache__/auto.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d5788d56b3f334fae591fa789aea744ae83663d GIT binary patch literal 1109 zcmY*Y&u`N(6tvI~fi!ce$52xf{9|3H;UjhQ4&H_i>KT370oMI~W0`nKkle=wq~pxZ@!YodUkN zy^i#|HH53@1K0{V4@h$9JK(oqKVP)Qt~!5-xXAf&p=mzNOghN~jb)z8B0Hi}EjS$tW~NH$E+KD(8A(oIA+xDT zWkIFlLiGq)r2})o^DdhjSx6)Hr5q3FVG8)Ep=mym%FsiminK`dHCi7PaViH_0d4qz z?lTP>GNv^lyP74UM>aM#=upX=UL7*q(ydFsW!t(}LES<@L38k950TY6rZ@ z1*n6zFEH3PF7Lbt+FPF{LZ!JV49oUvl4T=J3aJD@{FoV$NOc6hX9>i1hnvIhwFy%P zU<3liQRr|C^>tJOLfdV(^rNU?xrm~YL{TpJG_!sXMF&%s)tR42*$%7B-xtq%{ar1T z?(f7Zo%EjzJvMUEPbMbPBL-#f8==jk{_AwVuTvvdCoCRAwRGQdd}x*EPL8Vc?^|vT z7#~;b|1MzT9?Z8Ev;@W=gHO;o8sZe4;P3d{UIZUMCANZ3s2+iqF{nC(N?)tqwWlsu zYAkn{N_2aLb~$s}phE`aVV7O0V6C@dosQ9&glP>3j1)KSD_8sAPQof!|d(>7hw zHG9M#u;35)2-dS@)n8zNxXyzXTejuvbB}$_bj&2lOiu}kx9ub@e-7-$fSHmWg9BId__M|Ug36ZCsnVS zIG#fZINX{NsWD!SYf0U!W2^vGsB?$cJ|ib))=lPsr1y&i8nZ8yn2s3@%vkf*l67yL zl0%X{+*w5Jn<%2z5Y_aWhL^8HbH&S>d?U2H4Q}m|*5>6`XLqVa66k0g#PN)cPlQmw zjzFISq->-=^2AMrh9eixsG$7f)LtHr_*7_QY37S50G+|!=fY;QZp@s zG=zt9zydBLi&&H{Pqv@zwAzmAy390OoojTQ^jb78o(D4ZD&77}pNQ0}b^EVDrV#ID zSzZ~7REZd_DS}&8UK{Mb>-oL6-TvVquXg)Vgc6iOmfCxiGC-y%4X-VOrn#eZFg}gp z4C360ve|jWL(mF&ibhCYUc4HScTM)*MFuiTAk{&tQ#SldByj+82f%+ zP&1X=#xQ)9l167aDV5*vmM11uV6! z7^3Rl#TYUjiS*`j*_dD9`}lo){~M9_Orn{7_g=JzC|0WB{#ZuY@Fl2IEwW*lXKR2^kZa-TH>6lup2RIdNo7OFh*0I~PiK9-} Q=DRyq6W5|u{8KLc1*@zfd;kCd literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/cli.cpython-39.pyc b/lib/tqdm/__pycache__/cli.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..685e60c48e6931e1cfdb9f756681bafe8fdd41c4 GIT binary patch literal 9079 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 aCf4Y=(y4bazvv<65kpkl!e5Iv?f(E^Q}mkv literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/gui.cpython-39.pyc b/lib/tqdm/__pycache__/gui.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b90cad84428148480f5940fce9bc0acb8357db6 GIT binary patch literal 4533 zcma)A&5s;M74Pcr>6xAV8t;eKvB!y%c#Jb%CnA6`>x9@!5X3}sK1gd(r+2D%+_OF1 z9A4Fb58BK(ifd9wRs)gcN?Sdc0ZtM33s# ztLmy(Rj=NAzt`i>%s2|3*M7gsf4ih8f2YRLM@Qo&l=LPDS6qcNu13sdsI^G#Yp%u= zu5%;O`-W>s-UM&D7I=%>ywX)e=Gt9_JHKIW-QA58aS>Q zRNWd^Zz!$V(Fe}!em01bEDE=pufFx2=0GG}5v1vsFPc2)B*M=U(cDG}vp_b|w&T2& z`rY7?v$3(!+!jf{ncd?3c6S&y!~P%7xYh0?{R?iT z+3DOuxX%&%@tPGmHQF~q!(tn$qp@i^wFk`^y^>(1y`WBDrc_t z6J;Bh!nJ$0tJ76Lod!3#h1#SGKyCd*(G}N*Y8_!>`sn=8%{L#@_3c2UVG=i!ZKHrvk=8nMFw(Dy)JmLg}+HSpqf{sbgK%T3;W-B64+N>?>nzFju*Lnc61D2KUvm znOT{gD|gjiC1)Yte9S&#n4$C>vB{MiN>=4oR-;3F2x-RgCuL zc=ciXvXae%U*NU7D&8#QMsJa9>_fIm?Z(*3mB}&e$Ifl844L?}OA?kOU6ynuKACG> zG=Fq&gxXollux* zA$iB8JgavKGS(o2R<|ZkP2Zn^{HL*xKT{p6`~-huS()zO8T9`fntPU?#6Hv>M8~gZViJs3ymz@>?Bb#6b0*! zR>`Zwv>6=nq6>aBxal9jsL08jH1j(+U8-v^)n<@K#Mt{iYoC@lD62cFwf3#~I0t`?-( zs75`3Xo|3VGs9#pN;(fTSKsZ2F;Rb4EMoG(Who&=6pDE|a-!r8CPl$^R;Xb%QZJ;g zmUhB|?X^^?g;Et-VWin!6u7#-8>VgrL!z)BW@+~Y`TXgRtqVmJ`;CH52KP$^9nE$6 zhk?+Y|B`PH_9#5MW?2_{CmHM&+8`NVnITRm{k*U}FO0*?^9qZHslOEkg0w3rt`}N= z5MfG`6}ITS;S0Z?3UX__@ORxRP92gyZu^mIr^78;FD*3ax3D}f3Svk#cDbMVg|Syw zBdT1TMixdC#=*t1dcLsJAoKQcV2A>@OPo`3{#FpVW)yUTm=~76dovyQabd{Ht%Whd z5hjq*3cV{rUe4Q#XkMWYaXi-{>ZKhY>Yd9%SPi}O01wcZYwSsl&vvnJvb{~KX2CA{ z<@)YSn96hdQ2XtmFz@&j{ag*CQ2pIjrO?wL+AefobkoAz0T6V{%Ms6zE}tb*CGs^= zo{=R0ga@pscwQ&+)70}GDF3QUun`8yyi2Hm?ThPL>Qdy94a|rq2VhnZX91 z-~3*t1*U#sYAYJecUT=`PJo+ZbDD#CH2)Bu+7C_W5f~uvwozU| zN$-Imj3~k(=SO%_JhUflU-{Vy1RbTPPPAMGmv@jv==j}5IFIKpD(R(lge&^JOJPgt zA;kBLp2<}Jh0?RqEl_(>k>Q14K|pSmS}{R@J*k1tP&krdN4x%}wBS}uqcLH3Y2>J=Eifp~S!n4G^NQ5ra)kBzxYeaz$dmzUdME)LPNtECQ&tXK1 z2|{79aY(KZ_id0LprlBpa65D#CNzIetK)7Qv>kO+Kh&!f_lQyn;z_aJM@h*=<)PSh zxlT)yYZlBaRL^^CY1&-iiyfLs4z93A7Q(AX7NY%8;wM4;DkeaS${2S`oeJL6C9F8$W+i8(pX54McXaUyX3j_(s7z~TT309aePvS}&yDBv zdOp9Sr=RAtREvKh;ixwh^j9ad`CL9P0kYbg>&;IVq$~?C_q)6{SI!O& zPMyzpRkR(n7kCvZ%$#i3ki4ws3wIfx+0n7LU(4Bb=?RU=@qB@_5}%X#9@36`NMWub zO&KrBGb|oC!(uMafZeQ(8+n7zuPXQ1;4{8}L}U?(hDv=>?i1Sz60Zg&UgKlDksrIQ z+*idJer$3wJ2h$YrTiFQ-q-f=Hm~+hqqV|U`Ei*LApMy<$=43O@5J>fFu(iK0lC5y zmOsBzqTn%fi5ijPAP=#TY#uySC7CHZsFL#z=+yyv1BSguz0~oDIg`cH07B=q0YZ7) z4{Kuvqh%|~&(&M{jU&zq&{x~*1q&ZA*8uu^S?NvW!-aD>3eyaE`(Wc^wEu~sjLsZ7 z?KBv)FC(h>QMv)<;c(YMf-=RNQRDr{Lvwr?n z*l6x*(DUCOHgEWG+I&5UlLNw*mg?HuNu0%gKPWj$a=O3x>ocEK+w&W(rwRtl^fG*p zxE2B(ATx7ofS=482*8^cKhny?Hz8DG>NMZVefy88F6_nnT;gmpJ zT$JyT3JgWynjJsB?Wg$q5dNJK=_wBoFJO>sQV8DNE9x(2nFzOrS#V8=M3kucJc-ee zO1P%P;yt%oVz$S_PF7eVK;-QNZf)Az50H-(dM^n9%_2amGl;^hs7Oc;GoZgQ@P}#8 zS}No7E08a)5_yAGvVzDTqycwr9z>b%A#<=w?*Gw;I7u?t$kq2`Lbyj+Qe}`tdmdDh zx<=;t-K1a~`BU00P3pU}w#0^)!Nn+P4}`FRP!~Uq@cHnk+V~x(ext;7Tde_;3EykL z5KyK9tLwmV>Xmp+*v;zdLOBMwuYX~ybCA)%n9^aKJTIZYR&Xq}8>%BH&2JNIUC_))*eqq;;H8Ra6H=t0tm(+wau+i4*q6(z*q{_Z YrVzS=i${W1(_swS+>ewKwyCrK0doU@(EtDd literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/keras.cpython-39.pyc b/lib/tqdm/__pycache__/keras.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5525f65a0d6d2aa08cbe4433b0d88a4b97e5f0e2 GIT binary patch literal 4907 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 literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/notebook.cpython-39.pyc b/lib/tqdm/__pycache__/notebook.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6f39a0adcf8cb9a7028bb6c59c229fc30793460 GIT binary patch literal 7394 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 Buhswn literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/std.cpython-39.pyc b/lib/tqdm/__pycache__/std.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc4b101c01619e19e9719fa7082d41de6230ba2f GIT binary patch literal 45371 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 literal 0 HcmV?d00001 diff --git a/lib/tqdm/__pycache__/version.cpython-39.pyc b/lib/tqdm/__pycache__/version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..918fe16e57ccacc9cce1cd016da5dd50dfc56dec GIT binary patch literal 499 zcmYjN!A=`75Vf5RNl2)2E$U%!Q6yLpM^sfvr4lJLAR&}?wZberLuzGry|pJu$v^Z% zIP{11+7rLP2|EcAWBFP0^yXP_(w&`c=IZRyA~V(i`lXd97N6w z5ev>Hto$A@Y%Fd$^Jn=98W7|`(Ttm%VH29T1zUfe)+u+r5?c>q@pG+)RxEz8QXH~1 zLi?HxJIm8M`UAyX_=v_TU50>!8EF%RR|Yc#EHgX~RcR^BbA%9-rM+;f91L#8=i|%C zco03iG;GN0u9O=eDW4s+;-*M%>{3M0P}G6Zn(D5>JSFvrl62kLc#Vhcx+Nv(Ov<<; z{cnEQ2xnGzM@6L#eKSUz_=827SEO}rrOgTxctu;v2bH6gb@xK+*}Ph5pP2EhUBS!f z>v3{pUE1V2Gpb5X(auO$$+RMAf2V*ZA+&UqT&VlRD#HCL&1UHoZQ?g|un8Ddi@N=} X&@j*Oi;r*n4gxNCpZ6U0+}r1`c8ZEK literal 0 HcmV?d00001 diff --git a/lib/tqdm/_dist_ver.py b/lib/tqdm/_dist_ver.py new file mode 100644 index 0000000..fb9c466 --- /dev/null +++ b/lib/tqdm/_dist_ver.py @@ -0,0 +1 @@ +__version__ = '4.65.0' diff --git a/lib/tqdm/_main.py b/lib/tqdm/_main.py new file mode 100644 index 0000000..04fdeef --- /dev/null +++ b/lib/tqdm/_main.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .cli import * # NOQA +from .cli import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.cli.*` instead of `tqdm._main.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/lib/tqdm/_monitor.py b/lib/tqdm/_monitor.py new file mode 100644 index 0000000..f71aa56 --- /dev/null +++ b/lib/tqdm/_monitor.py @@ -0,0 +1,95 @@ +import atexit +from threading import Event, Thread, current_thread +from time import time +from warnings import warn + +__all__ = ["TMonitor", "TqdmSynchronisationWarning"] + + +class TqdmSynchronisationWarning(RuntimeWarning): + """tqdm multi-thread/-process errors which may cause incorrect nesting + but otherwise no adverse effects""" + pass + + +class TMonitor(Thread): + """ + Monitoring thread for tqdm bars. + Monitors if tqdm bars are taking too much time to display + and readjusts miniters automatically if necessary. + + Parameters + ---------- + tqdm_cls : class + tqdm class to use (can be core tqdm or a submodule). + sleep_interval : float + Time to sleep between monitoring checks. + """ + _test = {} # internal vars for unit testing + + def __init__(self, tqdm_cls, sleep_interval): + Thread.__init__(self) + self.daemon = True # kill thread when main killed (KeyboardInterrupt) + self.woken = 0 # last time woken up, to sync with monitor + self.tqdm_cls = tqdm_cls + self.sleep_interval = sleep_interval + self._time = self._test.get("time", time) + self.was_killed = self._test.get("Event", Event)() + atexit.register(self.exit) + self.start() + + def exit(self): + self.was_killed.set() + if self is not current_thread(): + self.join() + return self.report() + + def get_instances(self): + # returns a copy of started `tqdm_cls` instances + return [i for i in self.tqdm_cls._instances.copy() + # Avoid race by checking that the instance started + if hasattr(i, 'start_t')] + + def run(self): + cur_t = self._time() + while True: + # After processing and before sleeping, notify that we woke + # Need to be done just before sleeping + self.woken = cur_t + # Sleep some time... + self.was_killed.wait(self.sleep_interval) + # Quit if killed + if self.was_killed.is_set(): + return + # Then monitor! + # Acquire lock (to access _instances) + with self.tqdm_cls.get_lock(): + cur_t = self._time() + # Check tqdm instances are waiting too long to print + instances = self.get_instances() + for instance in instances: + # Check event in loop to reduce blocking time on exit + if self.was_killed.is_set(): + return + # Only if mininterval > 1 (else iterations are just slow) + # and last refresh exceeded maxinterval + if ( + instance.miniters > 1 + and (cur_t - instance.last_print_t) >= instance.maxinterval + ): + # force bypassing miniters on next iteration + # (dynamic_miniters adjusts mininterval automatically) + instance.miniters = 1 + # Refresh now! (works only for manual tqdm) + instance.refresh(nolock=True) + # Remove accidental long-lived strong reference + del instance + if instances != self.get_instances(): # pragma: nocover + warn("Set changed size during iteration" + + " (see https://github.com/tqdm/tqdm/issues/481)", + TqdmSynchronisationWarning, stacklevel=2) + # Remove accidental long-lived strong references + del instances + + def report(self): + return not self.was_killed.is_set() diff --git a/lib/tqdm/_tqdm.py b/lib/tqdm/_tqdm.py new file mode 100644 index 0000000..7fc4962 --- /dev/null +++ b/lib/tqdm/_tqdm.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .std import * # NOQA +from .std import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.std.*` instead of `tqdm._tqdm.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/lib/tqdm/_tqdm_gui.py b/lib/tqdm/_tqdm_gui.py new file mode 100644 index 0000000..f32aa89 --- /dev/null +++ b/lib/tqdm/_tqdm_gui.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .gui import * # NOQA +from .gui import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.gui.*` instead of `tqdm._tqdm_gui.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/lib/tqdm/_tqdm_notebook.py b/lib/tqdm/_tqdm_notebook.py new file mode 100644 index 0000000..f225fbf --- /dev/null +++ b/lib/tqdm/_tqdm_notebook.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .notebook import * # NOQA +from .notebook import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.notebook.*` instead of `tqdm._tqdm_notebook.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/lib/tqdm/_tqdm_pandas.py b/lib/tqdm/_tqdm_pandas.py new file mode 100644 index 0000000..c4fe6ef --- /dev/null +++ b/lib/tqdm/_tqdm_pandas.py @@ -0,0 +1,24 @@ +import sys + +__author__ = "github.com/casperdcl" +__all__ = ['tqdm_pandas'] + + +def tqdm_pandas(tclass, **tqdm_kwargs): + """ + Registers the given `tqdm` instance with + `pandas.core.groupby.DataFrameGroupBy.progress_apply`. + """ + from tqdm import TqdmDeprecationWarning + + if isinstance(tclass, type) or (getattr(tclass, '__name__', '').startswith( + 'tqdm_')): # delayed adapter case + TqdmDeprecationWarning( + "Please use `tqdm.pandas(...)` instead of `tqdm_pandas(tqdm, ...)`.", + fp_write=getattr(tqdm_kwargs.get('file', None), 'write', sys.stderr.write)) + tclass.pandas(**tqdm_kwargs) + else: + TqdmDeprecationWarning( + "Please use `tqdm.pandas(...)` instead of `tqdm_pandas(tqdm(...))`.", + fp_write=getattr(tclass.fp, 'write', sys.stderr.write)) + type(tclass).pandas(deprecated_t=tclass) diff --git a/lib/tqdm/_utils.py b/lib/tqdm/_utils.py new file mode 100644 index 0000000..385e849 --- /dev/null +++ b/lib/tqdm/_utils.py @@ -0,0 +1,11 @@ +from warnings import warn + +from .std import TqdmDeprecationWarning +from .utils import ( # NOQA, pylint: disable=unused-import + CUR_OS, IS_NIX, IS_WIN, RE_ANSI, Comparable, FormatReplace, SimpleTextIOWrapper, + _environ_cols_wrapper, _is_ascii, _is_utf, _screen_shape_linux, _screen_shape_tput, + _screen_shape_windows, _screen_shape_wrapper, _supports_unicode, _term_move_up, colorama) + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.utils.*` instead of `tqdm._utils.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/lib/tqdm/asyncio.py b/lib/tqdm/asyncio.py new file mode 100644 index 0000000..ddc89b8 --- /dev/null +++ b/lib/tqdm/asyncio.py @@ -0,0 +1,93 @@ +""" +Asynchronous progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm.asyncio import trange, tqdm +>>> async for i in trange(10): +... ... +""" +import asyncio +from sys import version_info + +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tqdm_asyncio', 'tarange', 'tqdm', 'trange'] + + +class tqdm_asyncio(std_tqdm): + """ + Asynchronous-friendly version of tqdm. + """ + def __init__(self, iterable=None, *args, **kwargs): + super(tqdm_asyncio, self).__init__(iterable, *args, **kwargs) + self.iterable_awaitable = False + if iterable is not None: + if hasattr(iterable, "__anext__"): + self.iterable_next = iterable.__anext__ + self.iterable_awaitable = True + elif hasattr(iterable, "__next__"): + self.iterable_next = iterable.__next__ + else: + self.iterable_iterator = iter(iterable) + self.iterable_next = self.iterable_iterator.__next__ + + def __aiter__(self): + return self + + async def __anext__(self): + try: + if self.iterable_awaitable: + res = await self.iterable_next() + else: + res = self.iterable_next() + self.update() + return res + except StopIteration: + self.close() + raise StopAsyncIteration + except BaseException: + self.close() + raise + + def send(self, *args, **kwargs): + return self.iterable.send(*args, **kwargs) + + @classmethod + def as_completed(cls, fs, *, loop=None, timeout=None, total=None, **tqdm_kwargs): + """ + Wrapper for `asyncio.as_completed`. + """ + if total is None: + total = len(fs) + kwargs = {} + if version_info[:2] < (3, 10): + kwargs['loop'] = loop + yield from cls(asyncio.as_completed(fs, timeout=timeout, **kwargs), + total=total, **tqdm_kwargs) + + @classmethod + async def gather(cls, *fs, loop=None, timeout=None, total=None, **tqdm_kwargs): + """ + Wrapper for `asyncio.gather`. + """ + async def wrap_awaitable(i, f): + return i, await f + + ifs = [wrap_awaitable(i, f) for i, f in enumerate(fs)] + res = [await f for f in cls.as_completed(ifs, loop=loop, timeout=timeout, + total=total, **tqdm_kwargs)] + return [i for _, i in sorted(res)] + + +def tarange(*args, **kwargs): + """ + A shortcut for `tqdm.asyncio.tqdm(range(*args), **kwargs)`. + """ + return tqdm_asyncio(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_asyncio +trange = tarange diff --git a/lib/tqdm/auto.py b/lib/tqdm/auto.py new file mode 100644 index 0000000..206c440 --- /dev/null +++ b/lib/tqdm/auto.py @@ -0,0 +1,40 @@ +""" +Enables multiple commonly used features. + +Method resolution order: + +- `tqdm.autonotebook` without import warnings +- `tqdm.asyncio` +- `tqdm.std` base class + +Usage: +>>> from tqdm.auto import trange, tqdm +>>> for i in trange(10): +... ... +""" +import warnings + +from .std import TqdmExperimentalWarning + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=TqdmExperimentalWarning) + from .autonotebook import tqdm as notebook_tqdm + +from .asyncio import tqdm as asyncio_tqdm +from .std import tqdm as std_tqdm + +if notebook_tqdm != std_tqdm: + class tqdm(notebook_tqdm, asyncio_tqdm): # pylint: disable=inconsistent-mro + pass +else: + tqdm = asyncio_tqdm + + +def trange(*args, **kwargs): + """ + A shortcut for `tqdm.auto.tqdm(range(*args), **kwargs)`. + """ + return tqdm(range(*args), **kwargs) + + +__all__ = ["tqdm", "trange"] diff --git a/lib/tqdm/autonotebook.py b/lib/tqdm/autonotebook.py new file mode 100644 index 0000000..a09f2ec --- /dev/null +++ b/lib/tqdm/autonotebook.py @@ -0,0 +1,29 @@ +""" +Automatically choose between `tqdm.notebook` and `tqdm.std`. + +Usage: +>>> from tqdm.autonotebook import trange, tqdm +>>> for i in trange(10): +... ... +""" +import sys +from warnings import warn + +try: + get_ipython = sys.modules['IPython'].get_ipython + if 'IPKernelApp' not in get_ipython().config: # pragma: no cover + raise ImportError("console") + from .notebook import WARN_NOIPYW, IProgress + if IProgress is None: + from .std import TqdmWarning + warn(WARN_NOIPYW, TqdmWarning, stacklevel=2) + raise ImportError('ipywidgets') +except Exception: + from .std import tqdm, trange +else: # pragma: no cover + from .notebook import tqdm, trange + from .std import TqdmExperimentalWarning + warn("Using `tqdm.autonotebook.tqdm` in notebook mode." + " Use `tqdm.tqdm` instead to force console mode" + " (e.g. in jupyter console)", TqdmExperimentalWarning, stacklevel=2) +__all__ = ["tqdm", "trange"] diff --git a/lib/tqdm/cli.py b/lib/tqdm/cli.py new file mode 100644 index 0000000..4442628 --- /dev/null +++ b/lib/tqdm/cli.py @@ -0,0 +1,311 @@ +""" +Module version for monitoring CLI pipes (`... | python -m tqdm | ...`). +""" +import logging +import re +import sys +from ast import literal_eval as numeric + +from .std import TqdmKeyError, TqdmTypeError, tqdm +from .version import __version__ + +__all__ = ["main"] +log = logging.getLogger(__name__) + + +def cast(val, typ): + log.debug((val, typ)) + if " or " in typ: + for t in typ.split(" or "): + try: + return cast(val, t) + except TqdmTypeError: + pass + raise TqdmTypeError(val + ' : ' + typ) + + # sys.stderr.write('\ndebug | `val:type`: `' + val + ':' + typ + '`.\n') + if typ == 'bool': + if (val == 'True') or (val == ''): + return True + elif val == 'False': + return False + else: + raise TqdmTypeError(val + ' : ' + typ) + try: + return eval(typ + '("' + val + '")') + except Exception: + if typ == 'chr': + return chr(ord(eval('"' + val + '"'))).encode() + else: + raise TqdmTypeError(val + ' : ' + typ) + + +def posix_pipe(fin, fout, delim=b'\\n', buf_size=256, + callback=lambda float: None, callback_len=True): + """ + Params + ------ + fin : binary file with `read(buf_size : int)` method + fout : binary file with `write` (and optionally `flush`) methods. + callback : function(float), e.g.: `tqdm.update` + callback_len : If (default: True) do `callback(len(buffer))`. + Otherwise, do `callback(data) for data in buffer.split(delim)`. + """ + fp_write = fout.write + + if not delim: + while True: + tmp = fin.read(buf_size) + + # flush at EOF + if not tmp: + getattr(fout, 'flush', lambda: None)() + return + + fp_write(tmp) + callback(len(tmp)) + # return + + buf = b'' + len_delim = len(delim) + # n = 0 + while True: + tmp = fin.read(buf_size) + + # flush at EOF + if not tmp: + if buf: + fp_write(buf) + if callback_len: + # n += 1 + buf.count(delim) + callback(1 + buf.count(delim)) + else: + for i in buf.split(delim): + callback(i) + getattr(fout, 'flush', lambda: None)() + return # n + + while True: + i = tmp.find(delim) + if i < 0: + buf += tmp + break + fp_write(buf + tmp[:i + len(delim)]) + # n += 1 + callback(1 if callback_len else (buf + tmp[:i])) + buf = b'' + tmp = tmp[i + len_delim:] + + +# ((opt, type), ... ) +RE_OPTS = re.compile(r'\n {8}(\S+)\s{2,}:\s*([^,]+)') +# better split method assuming no positional args +RE_SHLEX = re.compile(r'\s*(? : \2', d) + split = RE_OPTS.split(d) + opt_types_desc = zip(split[1::3], split[2::3], split[3::3]) + d = ''.join(('\n --{0} : {2}{3}' if otd[1] == 'bool' else + '\n --{0}=<{1}> : {2}{3}').format( + otd[0].replace('_', '-'), otd[0], *otd[1:]) + for otd in opt_types_desc if otd[0] not in UNSUPPORTED_OPTS) + + help_short = "Usage:\n tqdm [--help | options]\n" + d = help_short + """ +Options: + -h, --help Print this help and exit. + -v, --version Print version and exit. +""" + d.strip('\n') + '\n' + + # opts = docopt(d, version=__version__) + if any(v in argv for v in ('-v', '--version')): + sys.stdout.write(__version__ + '\n') + sys.exit(0) + elif any(v in argv for v in ('-h', '--help')): + sys.stdout.write(d + '\n') + sys.exit(0) + elif argv and argv[0][:2] != '--': + sys.stderr.write(f"Error:Unknown argument:{argv[0]}\n{help_short}") + + argv = RE_SHLEX.split(' '.join(["tqdm"] + argv)) + opts = dict(zip(argv[1::3], argv[3::3])) + + log.debug(opts) + opts.pop('log', True) + + tqdm_args = {'file': fp} + try: + for (o, v) in opts.items(): + o = o.replace('-', '_') + try: + tqdm_args[o] = cast(v, opt_types[o]) + except KeyError as e: + raise TqdmKeyError(str(e)) + log.debug('args:' + str(tqdm_args)) + + delim_per_char = tqdm_args.pop('bytes', False) + update = tqdm_args.pop('update', False) + update_to = tqdm_args.pop('update_to', False) + if sum((delim_per_char, update, update_to)) > 1: + raise TqdmKeyError("Can only have one of --bytes --update --update_to") + except Exception: + fp.write("\nError:\n" + help_short) + stdin, stdout_write = sys.stdin, sys.stdout.write + for i in stdin: + stdout_write(i) + raise + else: + buf_size = tqdm_args.pop('buf_size', 256) + delim = tqdm_args.pop('delim', b'\\n') + tee = tqdm_args.pop('tee', False) + manpath = tqdm_args.pop('manpath', None) + comppath = tqdm_args.pop('comppath', None) + if tqdm_args.pop('null', False): + class stdout(object): + @staticmethod + def write(_): + pass + else: + stdout = sys.stdout + stdout = getattr(stdout, 'buffer', stdout) + stdin = getattr(sys.stdin, 'buffer', sys.stdin) + if manpath or comppath: + from importlib import resources + from os import path + from shutil import copyfile + + def cp(name, dst): + """copy resource `name` to `dst`""" + if hasattr(resources, 'files'): + copyfile(str(resources.files('tqdm') / name), dst) + else: # py<3.9 + with resources.path('tqdm', name) as src: + copyfile(str(src), dst) + log.info("written:%s", dst) + if manpath is not None: + cp('tqdm.1', path.join(manpath, 'tqdm.1')) + if comppath is not None: + cp('completion.sh', path.join(comppath, 'tqdm_completion.sh')) + sys.exit(0) + if tee: + stdout_write = stdout.write + fp_write = getattr(fp, 'buffer', fp).write + + class stdout(object): # pylint: disable=function-redefined + @staticmethod + def write(x): + with tqdm.external_write_mode(file=fp): + fp_write(x) + stdout_write(x) + if delim_per_char: + tqdm_args.setdefault('unit', 'B') + tqdm_args.setdefault('unit_scale', True) + tqdm_args.setdefault('unit_divisor', 1024) + log.debug(tqdm_args) + with tqdm(**tqdm_args) as t: + posix_pipe(stdin, stdout, '', buf_size, t.update) + elif delim == b'\\n': + log.debug(tqdm_args) + write = stdout.write + if update or update_to: + with tqdm(**tqdm_args) as t: + if update: + def callback(i): + t.update(numeric(i.decode())) + else: # update_to + def callback(i): + t.update(numeric(i.decode()) - t.n) + for i in stdin: + write(i) + callback(i) + else: + for i in tqdm(stdin, **tqdm_args): + write(i) + else: + log.debug(tqdm_args) + with tqdm(**tqdm_args) as t: + callback_len = False + if update: + def callback(i): + t.update(numeric(i.decode())) + elif update_to: + def callback(i): + t.update(numeric(i.decode()) - t.n) + else: + callback = t.update + callback_len = True + posix_pipe(stdin, stdout, delim, buf_size, callback, callback_len) diff --git a/lib/tqdm/completion.sh b/lib/tqdm/completion.sh new file mode 100644 index 0000000..9f61c7f --- /dev/null +++ b/lib/tqdm/completion.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +_tqdm(){ + local cur prv + cur="${COMP_WORDS[COMP_CWORD]}" + prv="${COMP_WORDS[COMP_CWORD - 1]}" + + case ${prv} in + --bar_format|--buf_size|--colour|--comppath|--delay|--delim|--desc|--initial|--lock_args|--manpath|--maxinterval|--mininterval|--miniters|--ncols|--nrows|--position|--postfix|--smoothing|--total|--unit|--unit_divisor) + # await user input + ;; + "--log") + COMPREPLY=($(compgen -W 'CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET' -- ${cur})) + ;; + *) + COMPREPLY=($(compgen -W '--ascii --bar_format --buf_size --bytes --colour --comppath --delay --delim --desc --disable --dynamic_ncols --help --initial --leave --lock_args --log --manpath --maxinterval --mininterval --miniters --ncols --nrows --null --position --postfix --smoothing --tee --total --unit --unit_divisor --unit_scale --update --update_to --version --write_bytes -h -v' -- ${cur})) + ;; + esac +} +complete -F _tqdm tqdm diff --git a/lib/tqdm/contrib/__init__.py b/lib/tqdm/contrib/__init__.py new file mode 100644 index 0000000..7338c96 --- /dev/null +++ b/lib/tqdm/contrib/__init__.py @@ -0,0 +1,92 @@ +""" +Thin wrappers around common functions. + +Subpackages contain potentially unstable extensions. +""" +from warnings import warn + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmDeprecationWarning, tqdm +from ..utils import ObjectWrapper + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tenumerate', 'tzip', 'tmap'] + + +class DummyTqdmFile(ObjectWrapper): + """Dummy file-like that will write to tqdm""" + + def __init__(self, wrapped): + super(DummyTqdmFile, self).__init__(wrapped) + self._buf = [] + + def write(self, x, nolock=False): + nl = b"\n" if isinstance(x, bytes) else "\n" + pre, sep, post = x.rpartition(nl) + if sep: + blank = type(nl)() + tqdm.write(blank.join(self._buf + [pre, sep]), + end=blank, file=self._wrapped, nolock=nolock) + self._buf = [post] + else: + self._buf.append(x) + + def __del__(self): + if self._buf: + blank = type(self._buf[0])() + try: + tqdm.write(blank.join(self._buf), end=blank, file=self._wrapped) + except (OSError, ValueError): + pass + + +def builtin_iterable(func): + """Returns `func`""" + warn("This function has no effect, and will be removed in tqdm==5.0.0", + TqdmDeprecationWarning, stacklevel=2) + return func + + +def tenumerate(iterable, start=0, total=None, tqdm_class=tqdm_auto, **tqdm_kwargs): + """ + Equivalent of `numpy.ndenumerate` or builtin `enumerate`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + try: + import numpy as np + except ImportError: + pass + else: + if isinstance(iterable, np.ndarray): + return tqdm_class(np.ndenumerate(iterable), total=total or iterable.size, + **tqdm_kwargs) + return enumerate(tqdm_class(iterable, total=total, **tqdm_kwargs), start) + + +def tzip(iter1, *iter2plus, **tqdm_kwargs): + """ + Equivalent of builtin `zip`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = tqdm_kwargs.copy() + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + for i in zip(tqdm_class(iter1, **kwargs), *iter2plus): + yield i + + +def tmap(function, *sequences, **tqdm_kwargs): + """ + Equivalent of builtin `map`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + for i in tzip(*sequences, **tqdm_kwargs): + yield function(*i) diff --git a/lib/tqdm/contrib/__pycache__/__init__.cpython-39.pyc b/lib/tqdm/contrib/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..158271b1bf9255e8109d2cc44939e16fd20f3a95 GIT binary patch literal 2988 zcmcImO>^7E8Q#SQ2||>j$c+;B!^QN_4ktCOcqWHoQjkCZ zS}g341D)h)PI3Q&l<6gh{+M3(+EdQC_0m4?QdA`$dJ8za*iY`e&-=X3i+FwAXLx@9 zkDKD3J;we;ovV+7&ig3!Ur`AbGQowF^O+S|oC#Yvxjl12*RFtSo{_QzR-Yi+bq$d(F6t zlcRW=)|f6-jBQn^(n4i%p3j4(s8u}9(;)o^0vSKO;DePJQaD131>dDfy#o9De2hDg0)9!@5B(7nvNb-TLeVo_K zQW>jMJ8F?tsAqAN(AifX`=V3dMI&V)hZiitL;FiMfiHyp+zTDi6Rzk0F_*9gQk^fE z!@@4m^7Z=tW;UCX*$=ZkUHrmyg9+NV^Xw=M)IqF*XIY*D&`hCU26WeiF1~uGO_orp ze~3yk#ani2omnkE=bTMh%OYDaoXB?vj&|z?SCzyr?L|>mWGafZ6OEe*Ky>OfpXlz= zf#Mskdr_3+aa~8zH|*b!Ke#vga}CFi_7a&@qx)%nq{?bEtyEMW#3Gd=mDcL+=utKv z)iC+?%Eu!z{0=!2kGM@az_$FU$UxQ`1TKq6sAx;ZDln%-VD0P8K zv6d@-YRL`33rkohcFTc*ea=VriF4wfbXr$_uI$!6<)`eVtDHl(?Vfd7cGzv5)}30f zvvb~d(f3+-?<>Sg+nEB(s9W@D)md+esKL_N{f^^QUvnRvvC~3mhdie{MVXh$5!~x% zbw(^Ml2p6nIgru5tYWD$q9<52ha>3L+BN>w&S9Aqa-BxJ=)wiML!nfNfhRX;f`1|3 zptn`z+6LFPU8i7ZRn`iJh(lOov6c)cloO|s$2p4My{Ncc(C@JDuiYW1; zXjY0Qr*=Pzj+;2Yn4y0!lPEHDUYa_<5qkY1e3CTB*;j1Z#H=*_p-7}&O_cVyWAz<> z&BW(5FAzX3iXlARO#yplgoY4q2}ftK@i~Ax z@RvahZ8rKcP6L_F%BQIapu-fa@4ox?aA&w9DdwP_K6soQ<>}Kj2gt+-NycEtp@Xhm zjP9g2$4!>2tcaiil4Sf%jG7BF%i#`)C*Q)zZeoHSEqW-N34=eO)Du*TzDa8A#07u0 zz#l%pb;+IK4pUakTKCXK&_8PJ)+OehbBM3qTGelfeHm|^zw;M-58T5F5@yV;Mq9{* zLG5pTfak$+lRb@dq@bXj1p7z=)qGfp%WSY8lrp%uSg`-UVPrGf{!uK!65O{o-P@Os z=@O<(B@Kdm!KWgf#7(a58LWqKqsk$z_-tqvn`qe8t}0cW54zelw&=A#%&JlfWV=gO2uAj)49vNRYgX?MQW(R_Qg*2YL*rO?bvy zn;Zqb;=2eKJ!o9*8K!hal9kQQT9R_c&kdX2gEEw3^;87Q{eZ48LW{1fR*+CvBC$<_kh*zU}-(j@>U;vx>u*iO^PJA z#63$rghx9B{4yuJhw0F+l`v|g-9}}3 zEs1%g)mV2>N@z9I86TC-yQdraCeBIf;SDfaJ?^wyM_9Jdd`Z}Pt7 PbI;!Vk=Nb4;cfgEATQ%( literal 0 HcmV?d00001 diff --git a/lib/tqdm/contrib/__pycache__/bells.cpython-39.pyc b/lib/tqdm/contrib/__pycache__/bells.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c971c42204ede22be92d0a0481e66609fb17746 GIT binary patch literal 1027 zcmZuv&2G~`5Z-lM$90o5EkXz`_<$-R)E|1R5TYc3Rt>3Wf+{Ra&epqiO#KsfH?1nv zQzdSQS7?tMcoCie`O2xUz=>HWX@lCec4p_t-+ntYq+GTTtgW99X|sUPA7xB06O31| z#cvQ4If!Da1)76tf$EXwXrO1JLaaMFMog#1Ip(iH7nPNgE6(vQHAbCqL78 zg$!ldv!9YE0xz=o!}>w|kp#*xhNi&w4h1+z!^aHoM+-c zyHnrYYupJvi2-uE-E57(GhIC-GbATTz^-5(P`idRBVdvx$4)h8+-YsrcSe}b;*@8M zbOmKbHH|C9H4x6dJ09xX|KB-&;23a1F0YC6AWxn6ZU|o~^vno@1o{ayO`k{>r2Mot zpA|iiroQK?1GEzk<_Y0Rm;@rzQo(gaY(_-HKbm4{PFm#@$92tgH4|9{p#8pg4N($d zJc!s~7)i#n@|&R=rTx(bjxid9ABZog8YuC1x4z+i5R41AA(#J(i43` z8FwWUa@}o(2d)SuTOI&45-{PamaL9vX*D0omBDFdzKBygjMz(6XYmMxZkgD`C9R4V M@px-h&B7J@4{@>~EdT%j literal 0 HcmV?d00001 diff --git a/lib/tqdm/contrib/__pycache__/concurrent.cpython-39.pyc b/lib/tqdm/contrib/__pycache__/concurrent.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ecaf66c28c196c4a84e151f8b6e61ff117de2f1 GIT binary patch literal 3609 zcmd^CU5^|`6|Ji7nd#}-U9Zogbj+8kHnNBB^8<&DoGq+x9Xq_lLe~`&lb*58G(B zWIOBR!LaMvoh;0I!yej!?2c(lhbuCay)hfECQQB{SLNDAbV`S7@{(Nthzu{u%QD1! zU2e!8-k0T-Ez*DS=z92IFD>|EC8{b>nhRA<3du)tS;P~ilEMsjCuX7&JsN~3(3kXS zvxZTV9GF}bVw@;QdRbD8&0d7H47%O8nnW>HGAC^IiEloXxt;g`?}tJaX)!+PjZ?EX z*%`!T{>HEy3k?-yoHe1@Q;CpKE~=(isWMKqb{!Jiu2HE3&V9eZT2oC1H#j252~G z6XUHf5Fh&&kvWhQ4|_*VFPqAp|p-yZzv*7naeZf$!jR%x~U<3#V9 zvf3V3CenLCCTiP2wKuo#raRj@HOcFhi1%@0dfV>vhP_3V?%4O@uB*eQmlPWJ?e+tD ziC#xVycKGHA#KxuwwbyD*}2QGC+MR56h;3PRqZ_`FupHo?LVT58){8`l}yo>^fsye zr%Zard&Z7iuI(FNwoGeEWcy2G${=q$c}E6lcU-$GL$m|au3L2=dr#QM2glu-)aZZE zsk^&Wt~~KR{>gD@0?Y_M=+<Qu=GUOH=IqTePP1Az?(W`asdtqvU*V zi`*pok2e4j`+Fbd;nLh<9ix9<9+j&A%$k4;laS+iC$F+3hXVRo8v}^l{xYd~kk?|3W6aVv?C#+{y=HV#-CMIkBz_rdVut;^tb!DNCPys%*ROsTc7Y_R=CXsmP!eJjiy08tZ%g zu<_%vI#eDeHC|O#P1o7T+M@ArP-VAqa8;{zj-RF4U_?~?-f-o9S!Qn?Br#^IhV2$j z7jB}&PL}AV=N8(3Y&UA!v$s2o(@4E+*R?7RS+?J>;UZ%g&|NSU~-Mv?-(N6^zE#42x&MaL%c|0jY}=B&WxKs{xPIWXx)6;SEqT z3u?<+;*$jk-}-QpJ`x~j;{a&Sz6oVEcZ=)%o2N(Z_eTyeQl*c;XFG=%@&Dj~L+`T1 zz6fT^%DRKd+=kXq9BYoaqfKr~KG6x^EfwDpfbHi)ID*f0{@e!dii0$tVJBhM( zSU3fC)0KE!e2-V)>;!aPo~gCOit`9~fdvLpWU&}UFoivgea>_Sf%zf7|;~BrEQ#Pf?KG@se_c2Ok%O)qsEd)^{C$;|)F&zoEegbgHuKQNj z>}h}~z+?#a@7^Y+`!7J-6Td@jcSihQg7*F_d-#7w?FABh4SrEZe#x_`FWt;ucf`*nnxpMi? zJi09$_sRu|xww%21@Q=;?IU;;N56V!9#i-%OXjageuv+@apMNxnHX^GA_R_js(Dcw z7|aYf5_8RSafqHu%ZbhoxlG0ic^%IY^wYT+Ig)1ZC35jN8SpKnt7i*-|K^B;N^@kV z17&?3I)&OG3(S&7Nj3{#oqTqNEB!^ZdWJeH$|a zdJkEFOAa=gP87*9j-n>CX)OMXWa*Bw{|=g3JYL^2vzRd}gWj$s08G71C zcakQIB8=^o3d><#k!4XdOBrlLb~LK2j!O~Voxp|p;C5chNtWEPfm_?Gz@H_{4Ci1W O4Ln9yR)ed-#y_x^<6?$rV`Zj$z=R4my9nH`C z7Sh}Qe4eXwmh~^HoK00!eveoE0~xmhi`(2u?7+s?O`M(^xVAOCfSQ+hy;4x>m4mWv zePE@RJxf%M-n1=xM1_|g+kdq`v{L7q#ml^sRC~3+w=G`fwPdbW59%oUe6DN9b}-+y zc>OCoSa{3g^L*hmi!X@!V>dX5`Z>Oc`l9erU*zZc(q~q1UMvPRaXwh$&W_c1X83P^ zN2FY_L7zujD5f(OvCUYuGs)RrrklS1zKXhH-M?|;2J6VI$Mh$>*KB90mhoPbkB7#+ zekQf)TVYy8X;&0~WRk@!PRDJ_uU#TBorLpce zn@vUuU*G@5N2)D_NY#F(KVII~y06yP*5Y2bX*ShM)@J>y^K#b{016(OJ6)L)QkOWPysv2)^_0@?~_l#WZEmQug?UbA2iHV)+suWd+S;2N=1JYYSc2%`pO`+{}j z`yyqn8IW4d?Z#qWQUfT%2qdqDVVuS~45fz?S&;|y!1fE809K08|B7; zxHN@rb0V`g8Nno#P#}10;8kBD8`-J~h1nzf#3EdtIw$t2MRh>HIdtSJ+}p8Gc8{Hr z!@Yqe-!Lttc4v5bWSw{;=b*$(Bm0uYOP8!u=hy{kUIS>#M}GroJb>o1uAEeLZR8c8 zF>vvYy-&S#ftxSEW#sNyGrEacn|tz4`92jrCQCQ z3%dE%p3klAZ|zpY&nvR{8z2Jh_2m){DoJK}Sq=7ju?{@JV=bSh_KTEVqU_sTUGfJV{u`fd@FFV6?Gqo64H7fEv ze#!kX>}7nA5NPXR_{ktj#t^QAAJ4N+#!rT;h@+~+QQ$4OH7lAOJSbs-HDUaZa zT7=*x*^#087TX(W79}cU>c{;TahaLsc67va1j3702YjIfOaV?0&aNg532A z%hKe?>~+dkAfsNa6j(wv*eihLeV8nZ;Y7&?k2zs|Jj?Zovl|<4|ELMl;d);{sj3-g zYa(4sh!geHzV~%6xiL9Q)`ti{j_~QMHFmu%EZaKka9a%GX8lNEBYPsr9u%VZ>8_sy zX#SeIf2eO6=abQ1B7R~BK$D#f1E}66-#bZ!y$W7pQ}qzp$N~4jnW#UbMC{{e=fvgC zseR&Ua0|H^T}EGpySmDWL%>C-l~5~F?J>Ekn)Z3c%vI$T)8_E%bxY6bIyu0D`H>xC zt-sq}U_M?0e*Nf6Y;3GE#_mQE{sX60)27vFnl>5d!`M0lx2#M6#hcSG%=Ax3)9F9r zq7w}gz0O{Kbno5G+u`Qc&c>bhHp6>&{&Z{m_~QcgG;-!zXaP{)4=per#I`p7zg0Hg zzPY`9>z#0G^XyVRcvPa+`=C~=)k(65aBuuzl+3oo*lmpL4SHlRvy;5qeEnks{rg$W znFN$s5`$UC=17Ox5s1s4B!a0^YOxNmmI2}t0G^0_wrXC1W z>=XBW;^9662QA-(E4QDwPD*2!UqUDf*H5k2t+WbfZc6yCe1gdEplW6_rek70i=6a! z;O|8;gd%&99+t18vUX!wdxY=$tJem{!x9ybhnF5DAr+<_kEE$LYBQ-BeZ%7)1#`uG zh_~BXHgP2RGG(t*Mh-Wqj|Uh_Z4`o*zd=v#c6$2zLS9El3vUrJAxtXUbqLz~4&M4Z z->uri3r~m{;-+mpC1nac4PmXIFq86GN+Bt$W#k^@TExaACudu}gntm+Pcf30+PJnz zxr+AL$>gu8`aO_bM;j^Dcy!C`f4KC-j?GkX#S~CT3P_i2_8rUlID@L_aBTK ze00zVMb~0TXTE;1?i)C_(J)Pfc@o4OyhfmfAih9sFaaA5Y!7el?BjabhR-J}FF0Tv z&Q7kNCN5YuH>u^TWF3tacJ*puD2-OL;h5F)k{N*-#VJP1XZFvijiBGht7vnUW9p4( zisN5HOYUUK7;8{BrqnD#b#r?tt`l=x<~Dpw%1q`S1uf=oX9B~ZKG1QZ!UyKQC`mjN zv|$iVh@cH&8g873#whYC?4LFy>y*tRw5ieO)$2vTcY{2lB4vAi)vh~^eF4B+aOx<7 P3Eah(7JpSOd-neT>v>T2 literal 0 HcmV?d00001 diff --git a/lib/tqdm/contrib/__pycache__/itertools.cpython-39.pyc b/lib/tqdm/contrib/__pycache__/itertools.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8082b7c8d8c0c03fe2cfa6a770abef92b2e5bfa3 GIT binary patch literal 938 zcmYjQO>fjN5ValWvsW$Og(Cj8_dnvn0 zxRi7G3tb5zap104DV@k;kMriu_$6kGiva?A_47V=EQEfW;IYpIA{UrrMdqZoX3NS_RM{0eBWJj#`)G8BBIQgsF)|M) zwS9m_kJb>kYP+Ia=&)5gm0dYcQEiW30+-8aWzPvlm353VRFQA^9DiM}UFCi7D|C)( zuks*o8S{AeeZI36}N0z4} z6&A<0l7nXjF`ooJkpm8{vDN9_(sYUgSeHozka)Ka)66K5JLO)~WZiMz1z zXZOWM_pOA}cXtz!mEEmWo~WYi_DdDZ0pqFYs#L1=?(3}AmC(mZ$&wS+Pi5ECTTP0Q z5?OEcro&D-ZJKNWyLlbjiMgLz6mB)rYteZpNqcNU)6!C5}X zJNqP9J zMoFxv-Y}D1oN6I6Sc|q|Gx;Re+n&-qiMPDXBpZ$5bhO#;d;^k_HJ_FXgABGj)#E6Q zMnd|mbnn6ukMF&uZV;_ptB@ItFnU|NG<3Ya4%7LaL4RfsFHwTS7h5S-vsKb*Q!@F64$5O8G7p^%6uQD293v_q+6p{N4tH)$VxlxjvxIchqw^xt@koh^? zvkJ09cdP=xHm6@&pbK)qP$%GzD~#xtc81pUy~-y)N3Xn4^?|O9XB>JeXwSFMDcCSh zgzuIPxYRh(Mn`4qi3CYYM`bWqrnmyZ1c>@meD$z?X=)^wynpYX^EHwYeV$FU`GUH^R_#i|a)xoE$sdQY3c(Ro270K^ z4QtxB;5;J>LDjyJmtcHMmL}Q`Whuxl^5Bg<3NisINmft`JZzmjVi@7ggJ!@ARDL&e>wS`jbF>M$24uf8+Xu(b@KPlJ&^miPL3i!4Q7PdJ6 z7Muc(QpV?2;T3Qjem$FL=nWYGQ-PN!K`}T zr`z#h+dI4tm~3S5dRz>nNusZLf34Z-%Pi9m{Jz4q0Gh}p6u0&T1M%pI_Pk%U&D33(bn;U!bSoE9F>yp-T%;A zZEos=Ki@NY=`|4KS7h z%H6dO?)!)ICZE?!$2sfyN2lV+rW}BRz^i2L{qDp%(g1@oa`x`=t+<2_h}n!YwUOeLVtwug@0|h08;6@OzoB6Jt3Oef;&WK?n2;B#rJzCHm@^|YI1E9fl z;ZwPSAFH(bS4#(#0Z@1%k#w%3e-5&7e&bI-{4Km>*FRNp{p(3Kh!S-JI?bNf7{jz} z7{lab7+Y-6PyrgwuqS^B6uu?<@Hq_tH8ZoH@BGcsR;6f&2sPv+wL3|aWXz~hP-8G?B$7s++ z(uOQJ_u9sjiv7XqIz)L8u*pTl>IIYvejRwb@H&*eVitSp+OD-kJFk|g{4LNTa34OH zTM^AOc-1~M1wA0o2~;@NfhirI1G1Vv;5YPH!3q)s?*5bhjehn1UJKE@V4QtveFYUx z(W^$76~_Z9LAmLd_Tw^9XAWbD>Dd=6n!W~`q-%fyk_wD7WBFb-;aroL^)L9 zGw@M~0)I_V5Aru)|25;w5J@Suyo!XQXShJH0=)qWkE>L+LFMFeGhat}fP%C20%3nZJb})y)-zfN-C9y;) z9X-t<7fTyS7&a|z(8pPR_Am@gF;$@e%4qJM;oZQUDfHay nRk3>m3s7|t8fIhcbuEi71Luy_wU^GjE!VMN#(LR(*P{OiWF^z# literal 0 HcmV?d00001 diff --git a/lib/tqdm/contrib/__pycache__/slack.cpython-39.pyc b/lib/tqdm/contrib/__pycache__/slack.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac2f63bd84d2d994bee623b38c972dbb1cf8ec48 GIT binary patch literal 4331 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 literal 0 HcmV?d00001 diff --git a/lib/tqdm/contrib/__pycache__/telegram.cpython-39.pyc b/lib/tqdm/contrib/__pycache__/telegram.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f62a37ad125c1247fd356ab5a52a7797405c95c GIT binary patch literal 5278 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 NXRgisy5u_Me*xil3WERu literal 0 HcmV?d00001 diff --git a/lib/tqdm/contrib/__pycache__/utils_worker.cpython-39.pyc b/lib/tqdm/contrib/__pycache__/utils_worker.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cc9d6ac13d6cc50ed2ba19dd789e0ba46c9f8de GIT binary patch literal 1475 zcmZux&2J+$6t`!bnaLzAqDW{#Xf+3rHq~yr+9RycO3N-FEnP$xDn^l}j_oAV%vWuX z+C)<>-76e9w}=Z!ocUjT<i*0a7nu;wVNOb0@>O6Fb=QZsz7BCcqQk$vU|g`@nnLpW+n90S~4)?tYGVmj|yA z4@7w7#yuV$p+RqXKm79L0V@kuODPJrh-M&DuPL%7{iktj$@ZQ+kxUzXQ6VbLbN>!vmmX}ILQZP~INY&#!)mrfAUI{t~50xU~ zsbW-e!3V7;h5^ZDSl$JC3Zy=ONw3oOBW_TFwKGNBy>fteIf7S^!&_?rA?@Y#A~|10 zMoF9b0g={^Ar(Yf+Ta*}QtMg@1;>dJ+2l7Q`yl`OyQd$IzEuEkbi`y@jXo1sFBTU5S z-fHDzkas}tHQu#%gD`@<-*j#;M$5b4Cphgkc#0e67yJ!MQRDuKf5MmEF)AJrq{GF} z7+pdeEZ^6?>%QJ;{3~p2kJ0y#hfDtlzqr$`579&Gd$;kdzJZ$J7&784KRKDy1-rLT z!v@bVqCJN(0PVMSfWtEAEHiScg z8W*<4W3*rbI)YY}D7aP2uC?^qqBGrR7fe(-Eeq4NSxP9hndzKM$e(eQmd0yM2Z8Ka zLtIut*BxQ}b-f!bR)y0}J76r|g~`&YWRc_HT^zjmLhRtXAo 1000: + from warnings import warn + warn("Iterable length %d > 1000 but `chunksize` is not set." + " This may seriously degrade multiprocess performance." + " Set `chunksize=1` or more." % longest_iterable_len, + TqdmWarning, stacklevel=2) + if "lock_name" not in tqdm_kwargs: + tqdm_kwargs = tqdm_kwargs.copy() + tqdm_kwargs["lock_name"] = "mp_lock" + return _executor_map(ProcessPoolExecutor, fn, *iterables, **tqdm_kwargs) diff --git a/lib/tqdm/contrib/discord.py b/lib/tqdm/contrib/discord.py new file mode 100644 index 0000000..b8366fb --- /dev/null +++ b/lib/tqdm/contrib/discord.py @@ -0,0 +1,119 @@ +""" +Sends updates to a Discord bot. + +Usage: +>>> from tqdm.contrib.discord import tqdm, trange +>>> for i in trange(10, token='{token}', channel_id='{channel_id}'): +... ... + +![screenshot](https://img.tqdm.ml/screenshot-discord.png) +""" +import logging +from os import getenv + +try: + from disco.client import Client, ClientConfig +except ImportError: + raise ImportError("Please `pip install disco-py`") + +from ..auto import tqdm as tqdm_auto +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['DiscordIO', 'tqdm_discord', 'tdrange', 'tqdm', 'trange'] + + +class DiscordIO(MonoWorker): + """Non-blocking file-like IO using a Discord Bot.""" + def __init__(self, token, channel_id): + """Creates a new message in the given `channel_id`.""" + super(DiscordIO, self).__init__() + config = ClientConfig() + config.token = token + client = Client(config) + self.text = self.__class__.__name__ + try: + self.message = client.api.channels_messages_create(channel_id, self.text) + except Exception as e: + tqdm_auto.write(str(e)) + self.message = None + + def write(self, s): + """Replaces internal `message`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # skip duplicate message + message = self.message + if message is None: + return + self.text = s + try: + future = self.submit(message.edit, '`' + s + '`') + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_discord(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Discord Bot. + May take a few seconds to create (`__init__`). + + - create a discord bot (not public, no requirement of OAuth2 code + grant, only send message permissions) & invite it to a channel: + + - copy the bot `{token}` & `{channel_id}` and paste below + + >>> from tqdm.contrib.discord import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', channel_id='{channel_id}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Discord token + [default: ${TQDM_DISCORD_TOKEN}]. + channel_id : int, required. Discord channel ID + [default: ${TQDM_DISCORD_CHANNEL_ID}]. + mininterval : float, optional. + Minimum of [default: 1.5] to avoid rate limit. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + logging.getLogger("HTTPClient").setLevel(logging.WARNING) + self.dio = DiscordIO( + kwargs.pop('token', getenv("TQDM_DISCORD_TOKEN")), + kwargs.pop('channel_id', getenv("TQDM_DISCORD_CHANNEL_ID"))) + kwargs['mininterval'] = max(1.5, kwargs.get('mininterval', 1.5)) + super(tqdm_discord, self).__init__(*args, **kwargs) + + def display(self, **kwargs): + super(tqdm_discord, self).display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '{bar:10u}').replace('{bar}', '{bar:10u}') + else: + fmt['bar_format'] = '{l_bar}{bar:10u}{r_bar}' + self.dio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super(tqdm_discord, self).clear(*args, **kwargs) + if not self.disable: + self.dio.write("") + + +def tdrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.discord.tqdm(range(*args), **kwargs)`.""" + return tqdm_discord(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_discord +trange = tdrange diff --git a/lib/tqdm/contrib/itertools.py b/lib/tqdm/contrib/itertools.py new file mode 100644 index 0000000..e67651a --- /dev/null +++ b/lib/tqdm/contrib/itertools.py @@ -0,0 +1,35 @@ +""" +Thin wrappers around `itertools`. +""" +import itertools + +from ..auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['product'] + + +def product(*iterables, **tqdm_kwargs): + """ + Equivalent of `itertools.product`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = tqdm_kwargs.copy() + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + try: + lens = list(map(len, iterables)) + except TypeError: + total = None + else: + total = 1 + for i in lens: + total *= i + kwargs.setdefault("total", total) + with tqdm_class(**kwargs) as t: + it = itertools.product(*iterables) + for i in it: + yield i + t.update() diff --git a/lib/tqdm/contrib/logging.py b/lib/tqdm/contrib/logging.py new file mode 100644 index 0000000..4fd602e --- /dev/null +++ b/lib/tqdm/contrib/logging.py @@ -0,0 +1,126 @@ +""" +Helper functionality for interoperability with stdlib `logging`. +""" +import logging +import sys +from contextlib import contextmanager + +try: + from typing import Iterator, List, Optional, Type # pylint: disable=unused-import +except ImportError: + pass + +from ..std import tqdm as std_tqdm + + +class _TqdmLoggingHandler(logging.StreamHandler): + def __init__( + self, + tqdm_class=std_tqdm # type: Type[std_tqdm] + ): + super(_TqdmLoggingHandler, self).__init__() + self.tqdm_class = tqdm_class + + def emit(self, record): + try: + msg = self.format(record) + self.tqdm_class.write(msg, file=self.stream) + self.flush() + except (KeyboardInterrupt, SystemExit): + raise + except: # noqa pylint: disable=bare-except + self.handleError(record) + + +def _is_console_logging_handler(handler): + return (isinstance(handler, logging.StreamHandler) + and handler.stream in {sys.stdout, sys.stderr}) + + +def _get_first_found_console_logging_handler(handlers): + for handler in handlers: + if _is_console_logging_handler(handler): + return handler + + +@contextmanager +def logging_redirect_tqdm( + loggers=None, # type: Optional[List[logging.Logger]], + tqdm_class=std_tqdm # type: Type[std_tqdm] +): + # type: (...) -> Iterator[None] + """ + Context manager redirecting console logging to `tqdm.write()`, leaving + other logging handlers (e.g. log files) unaffected. + + Parameters + ---------- + loggers : list, optional + Which handlers to redirect (default: [logging.root]). + tqdm_class : optional + + Example + ------- + ```python + import logging + from tqdm import trange + from tqdm.contrib.logging import logging_redirect_tqdm + + LOG = logging.getLogger(__name__) + + if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + with logging_redirect_tqdm(): + for i in trange(9): + if i == 4: + LOG.info("console logging redirected to `tqdm.write()`") + # logging restored + ``` + """ + if loggers is None: + loggers = [logging.root] + original_handlers_list = [logger.handlers for logger in loggers] + try: + for logger in loggers: + tqdm_handler = _TqdmLoggingHandler(tqdm_class) + orig_handler = _get_first_found_console_logging_handler(logger.handlers) + if orig_handler is not None: + tqdm_handler.setFormatter(orig_handler.formatter) + tqdm_handler.stream = orig_handler.stream + logger.handlers = [ + handler for handler in logger.handlers + if not _is_console_logging_handler(handler)] + [tqdm_handler] + yield + finally: + for logger, original_handlers in zip(loggers, original_handlers_list): + logger.handlers = original_handlers + + +@contextmanager +def tqdm_logging_redirect( + *args, + # loggers=None, # type: Optional[List[logging.Logger]] + # tqdm=None, # type: Optional[Type[tqdm.tqdm]] + **kwargs +): + # type: (...) -> Iterator[None] + """ + Convenience shortcut for: + ```python + with tqdm_class(*args, **tqdm_kwargs) as pbar: + with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class): + yield pbar + ``` + + Parameters + ---------- + tqdm_class : optional, (default: tqdm.std.tqdm). + loggers : optional, list. + **tqdm_kwargs : passed to `tqdm_class`. + """ + tqdm_kwargs = kwargs.copy() + loggers = tqdm_kwargs.pop('loggers', None) + tqdm_class = tqdm_kwargs.pop('tqdm_class', std_tqdm) + with tqdm_class(*args, **tqdm_kwargs) as pbar: + with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class): + yield pbar diff --git a/lib/tqdm/contrib/slack.py b/lib/tqdm/contrib/slack.py new file mode 100644 index 0000000..c8fe713 --- /dev/null +++ b/lib/tqdm/contrib/slack.py @@ -0,0 +1,120 @@ +""" +Sends updates to a Slack app. + +Usage: +>>> from tqdm.contrib.slack import tqdm, trange +>>> for i in trange(10, token='{token}', channel='{channel}'): +... ... + +![screenshot](https://img.tqdm.ml/screenshot-slack.png) +""" +import logging +from os import getenv + +try: + from slack_sdk import WebClient +except ImportError: + raise ImportError("Please `pip install slack-sdk`") + +from ..auto import tqdm as tqdm_auto +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["0x2b3bfa0", "casperdcl"]} +__all__ = ['SlackIO', 'tqdm_slack', 'tsrange', 'tqdm', 'trange'] + + +class SlackIO(MonoWorker): + """Non-blocking file-like IO using a Slack app.""" + def __init__(self, token, channel): + """Creates a new message in the given `channel`.""" + super(SlackIO, self).__init__() + self.client = WebClient(token=token) + self.text = self.__class__.__name__ + try: + self.message = self.client.chat_postMessage(channel=channel, text=self.text) + except Exception as e: + tqdm_auto.write(str(e)) + self.message = None + + def write(self, s): + """Replaces internal `message`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # skip duplicate message + message = self.message + if message is None: + return + self.text = s + try: + future = self.submit(self.client.chat_update, channel=message['channel'], + ts=message['ts'], text='`' + s + '`') + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_slack(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Slack app. + May take a few seconds to create (`__init__`). + + - create a Slack app with the `chat:write` scope & invite it to a + channel: + - copy the bot `{token}` & `{channel}` and paste below + >>> from tqdm.contrib.slack import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', channel='{channel}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Slack token + [default: ${TQDM_SLACK_TOKEN}]. + channel : int, required. Slack channel + [default: ${TQDM_SLACK_CHANNEL}]. + mininterval : float, optional. + Minimum of [default: 1.5] to avoid rate limit. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + logging.getLogger("HTTPClient").setLevel(logging.WARNING) + self.sio = SlackIO( + kwargs.pop('token', getenv("TQDM_SLACK_TOKEN")), + kwargs.pop('channel', getenv("TQDM_SLACK_CHANNEL"))) + kwargs['mininterval'] = max(1.5, kwargs.get('mininterval', 1.5)) + super(tqdm_slack, self).__init__(*args, **kwargs) + + def display(self, **kwargs): + super(tqdm_slack, self).display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '`{bar:10}`').replace('{bar}', '`{bar:10u}`') + else: + fmt['bar_format'] = '{l_bar}`{bar:10}`{r_bar}' + if fmt['ascii'] is False: + fmt['ascii'] = [":black_square:", ":small_blue_diamond:", ":large_blue_diamond:", + ":large_blue_square:"] + fmt['ncols'] = 336 + self.sio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super(tqdm_slack, self).clear(*args, **kwargs) + if not self.disable: + self.sio.write("") + + +def tsrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.slack.tqdm(range(*args), **kwargs)`.""" + return tqdm_slack(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_slack +trange = tsrange diff --git a/lib/tqdm/contrib/telegram.py b/lib/tqdm/contrib/telegram.py new file mode 100644 index 0000000..b9a589b --- /dev/null +++ b/lib/tqdm/contrib/telegram.py @@ -0,0 +1,153 @@ +""" +Sends updates to a Telegram bot. + +Usage: +>>> from tqdm.contrib.telegram import tqdm, trange +>>> for i in trange(10, token='{token}', chat_id='{chat_id}'): +... ... + +![screenshot](https://img.tqdm.ml/screenshot-telegram.gif) +""" +from os import getenv +from warnings import warn + +from requests import Session + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmWarning +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TelegramIO', 'tqdm_telegram', 'ttgrange', 'tqdm', 'trange'] + + +class TelegramIO(MonoWorker): + """Non-blocking file-like IO using a Telegram Bot.""" + API = 'https://api.telegram.org/bot' + + def __init__(self, token, chat_id): + """Creates a new message in the given `chat_id`.""" + super(TelegramIO, self).__init__() + self.token = token + self.chat_id = chat_id + self.session = Session() + self.text = self.__class__.__name__ + self.message_id + + @property + def message_id(self): + if hasattr(self, '_message_id'): + return self._message_id + try: + res = self.session.post( + self.API + '%s/sendMessage' % self.token, + data={'text': '`' + self.text + '`', 'chat_id': self.chat_id, + 'parse_mode': 'MarkdownV2'}).json() + except Exception as e: + tqdm_auto.write(str(e)) + else: + if res.get('error_code') == 429: + warn("Creation rate limit: try increasing `mininterval`.", + TqdmWarning, stacklevel=2) + else: + self._message_id = res['result']['message_id'] + return self._message_id + + def write(self, s): + """Replaces internal `message_id`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # avoid duplicate message Bot error + message_id = self.message_id + if message_id is None: + return + self.text = s + try: + future = self.submit( + self.session.post, self.API + '%s/editMessageText' % self.token, + data={'text': '`' + s + '`', 'chat_id': self.chat_id, + 'message_id': message_id, 'parse_mode': 'MarkdownV2'}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + def delete(self): + """Deletes internal `message_id`.""" + try: + future = self.submit( + self.session.post, self.API + '%s/deleteMessage' % self.token, + data={'chat_id': self.chat_id, 'message_id': self.message_id}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_telegram(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Telegram Bot. + May take a few seconds to create (`__init__`). + + - create a bot + - copy its `{token}` + - add the bot to a chat and send it a message such as `/start` + - go to to find out + the `{chat_id}` + - paste the `{token}` & `{chat_id}` below + + >>> from tqdm.contrib.telegram import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', chat_id='{chat_id}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Telegram token + [default: ${TQDM_TELEGRAM_TOKEN}]. + chat_id : str, required. Telegram chat ID + [default: ${TQDM_TELEGRAM_CHAT_ID}]. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + self.tgio = TelegramIO( + kwargs.pop('token', getenv('TQDM_TELEGRAM_TOKEN')), + kwargs.pop('chat_id', getenv('TQDM_TELEGRAM_CHAT_ID'))) + super(tqdm_telegram, self).__init__(*args, **kwargs) + + def display(self, **kwargs): + super(tqdm_telegram, self).display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '{bar:10u}').replace('{bar}', '{bar:10u}') + else: + fmt['bar_format'] = '{l_bar}{bar:10u}{r_bar}' + self.tgio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super(tqdm_telegram, self).clear(*args, **kwargs) + if not self.disable: + self.tgio.write("") + + def close(self): + if self.disable: + return + super(tqdm_telegram, self).close() + if not (self.leave or (self.leave is None and self.pos == 0)): + self.tgio.delete() + + +def ttgrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.telegram.tqdm(range(*args), **kwargs)`.""" + return tqdm_telegram(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_telegram +trange = ttgrange diff --git a/lib/tqdm/contrib/utils_worker.py b/lib/tqdm/contrib/utils_worker.py new file mode 100644 index 0000000..2a03a2a --- /dev/null +++ b/lib/tqdm/contrib/utils_worker.py @@ -0,0 +1,38 @@ +""" +IO/concurrency helpers for `tqdm.contrib`. +""" +from collections import deque +from concurrent.futures import ThreadPoolExecutor + +from ..auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['MonoWorker'] + + +class MonoWorker(object): + """ + Supports one running task and one waiting task. + The waiting task is the most recent submitted (others are discarded). + """ + def __init__(self): + self.pool = ThreadPoolExecutor(max_workers=1) + self.futures = deque([], 2) + + def submit(self, func, *args, **kwargs): + """`func(*args, **kwargs)` may replace currently waiting task.""" + futures = self.futures + if len(futures) == futures.maxlen: + running = futures.popleft() + if not running.done(): + if len(futures): # clear waiting + waiting = futures.pop() + waiting.cancel() + futures.appendleft(running) # re-insert running + try: + waiting = self.pool.submit(func, *args, **kwargs) + except Exception as e: + tqdm_auto.write(str(e)) + else: + futures.append(waiting) + return waiting diff --git a/lib/tqdm/dask.py b/lib/tqdm/dask.py new file mode 100644 index 0000000..af9926a --- /dev/null +++ b/lib/tqdm/dask.py @@ -0,0 +1,44 @@ +from functools import partial + +from dask.callbacks import Callback + +from .auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TqdmCallback'] + + +class TqdmCallback(Callback): + """Dask callback for task progress.""" + def __init__(self, start=None, pretask=None, tqdm_class=tqdm_auto, + **tqdm_kwargs): + """ + Parameters + ---------- + tqdm_class : optional + `tqdm` class to use for bars [default: `tqdm.auto.tqdm`]. + tqdm_kwargs : optional + Any other arguments used for all bars. + """ + super(TqdmCallback, self).__init__(start=start, pretask=pretask) + if tqdm_kwargs: + tqdm_class = partial(tqdm_class, **tqdm_kwargs) + self.tqdm_class = tqdm_class + + def _start_state(self, _, state): + self.pbar = self.tqdm_class(total=sum( + len(state[k]) for k in ['ready', 'waiting', 'running', 'finished'])) + + def _posttask(self, *_, **__): + self.pbar.update() + + def _finish(self, *_, **__): + self.pbar.close() + + def display(self): + """Displays in the current cell in Notebooks.""" + container = getattr(self.bar, 'container', None) + if container is None: + return + from .notebook import display + display(container) diff --git a/lib/tqdm/gui.py b/lib/tqdm/gui.py new file mode 100644 index 0000000..d52b9b6 --- /dev/null +++ b/lib/tqdm/gui.py @@ -0,0 +1,186 @@ +""" +Matplotlib GUI progressbar decorator for iterators. + +Usage: +>>> from tqdm.gui import trange, tqdm +>>> for i in trange(10): +... ... +""" +# future division is important to divide integers and get as +# a result precise floating numbers (instead of truncated int) +import re +from warnings import warn + +# to inherit from the tqdm class +from .std import TqdmExperimentalWarning +from .std import tqdm as std_tqdm + +# import compatibility functions and utilities + +__author__ = {"github.com/": ["casperdcl", "lrq3000"]} +__all__ = ['tqdm_gui', 'tgrange', 'tqdm', 'trange'] + + +class tqdm_gui(std_tqdm): # pragma: no cover + """Experimental Matplotlib GUI version of tqdm!""" + # TODO: @classmethod: write() on GUI? + def __init__(self, *args, **kwargs): + from collections import deque + + import matplotlib as mpl + import matplotlib.pyplot as plt + kwargs = kwargs.copy() + kwargs['gui'] = True + colour = kwargs.pop('colour', 'g') + super(tqdm_gui, self).__init__(*args, **kwargs) + + if self.disable: + return + + warn("GUI is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + self.mpl = mpl + self.plt = plt + + # Remember if external environment uses toolbars + self.toolbar = self.mpl.rcParams['toolbar'] + self.mpl.rcParams['toolbar'] = 'None' + + self.mininterval = max(self.mininterval, 0.5) + self.fig, ax = plt.subplots(figsize=(9, 2.2)) + # self.fig.subplots_adjust(bottom=0.2) + total = self.__len__() # avoids TypeError on None #971 + if total is not None: + self.xdata = [] + self.ydata = [] + self.zdata = [] + else: + self.xdata = deque([]) + self.ydata = deque([]) + self.zdata = deque([]) + self.line1, = ax.plot(self.xdata, self.ydata, color='b') + self.line2, = ax.plot(self.xdata, self.zdata, color='k') + ax.set_ylim(0, 0.001) + if total is not None: + ax.set_xlim(0, 100) + ax.set_xlabel("percent") + self.fig.legend((self.line1, self.line2), ("cur", "est"), + loc='center right') + # progressbar + self.hspan = plt.axhspan(0, 0.001, xmin=0, xmax=0, color=colour) + else: + # ax.set_xlim(-60, 0) + ax.set_xlim(0, 60) + ax.invert_xaxis() + ax.set_xlabel("seconds") + ax.legend(("cur", "est"), loc='lower left') + ax.grid() + # ax.set_xlabel('seconds') + ax.set_ylabel((self.unit if self.unit else "it") + "/s") + if self.unit_scale: + plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) + ax.yaxis.get_offset_text().set_x(-0.15) + + # Remember if external environment is interactive + self.wasion = plt.isinteractive() + plt.ion() + self.ax = ax + + def close(self): + if self.disable: + return + + self.disable = True + + with self.get_lock(): + self._instances.remove(self) + + # Restore toolbars + self.mpl.rcParams['toolbar'] = self.toolbar + # Return to non-interactive mode + if not self.wasion: + self.plt.ioff() + if self.leave: + self.display() + else: + self.plt.close(self.fig) + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + n = self.n + cur_t = self._time() + elapsed = cur_t - self.start_t + delta_it = n - self.last_print_n + delta_t = cur_t - self.last_print_t + + # Inline due to multiple calls + total = self.total + xdata = self.xdata + ydata = self.ydata + zdata = self.zdata + ax = self.ax + line1 = self.line1 + line2 = self.line2 + # instantaneous rate + y = delta_it / delta_t + # overall rate + z = n / elapsed + # update line data + xdata.append(n * 100.0 / total if total else cur_t) + ydata.append(y) + zdata.append(z) + + # Discard old values + # xmin, xmax = ax.get_xlim() + # if (not total) and elapsed > xmin * 1.1: + if (not total) and elapsed > 66: + xdata.popleft() + ydata.popleft() + zdata.popleft() + + ymin, ymax = ax.get_ylim() + if y > ymax or z > ymax: + ymax = 1.1 * y + ax.set_ylim(ymin, ymax) + ax.figure.canvas.draw() + + if total: + line1.set_data(xdata, ydata) + line2.set_data(xdata, zdata) + try: + poly_lims = self.hspan.get_xy() + except AttributeError: + self.hspan = self.plt.axhspan(0, 0.001, xmin=0, xmax=0, color='g') + poly_lims = self.hspan.get_xy() + poly_lims[0, 1] = ymin + poly_lims[1, 1] = ymax + poly_lims[2] = [n / total, ymax] + poly_lims[3] = [poly_lims[2, 0], ymin] + if len(poly_lims) > 4: + poly_lims[4, 1] = ymin + self.hspan.set_xy(poly_lims) + else: + t_ago = [cur_t - i for i in xdata] + line1.set_data(t_ago, ydata) + line2.set_data(t_ago, zdata) + + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + if '' in msg: + msg = "".join(re.split(r'\|?\|?', msg, 1)) + ax.set_title(msg, fontname="DejaVu Sans Mono", fontsize=11) + self.plt.pause(1e-9) + + +def tgrange(*args, **kwargs): + """Shortcut for `tqdm.gui.tqdm(range(*args), **kwargs)`.""" + return tqdm_gui(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_gui +trange = tgrange diff --git a/lib/tqdm/keras.py b/lib/tqdm/keras.py new file mode 100644 index 0000000..96782d7 --- /dev/null +++ b/lib/tqdm/keras.py @@ -0,0 +1,122 @@ +from copy import copy +from functools import partial + +from .auto import tqdm as tqdm_auto + +try: + import keras +except (ImportError, AttributeError) as e: + try: + from tensorflow import keras + except ImportError: + raise e +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TqdmCallback'] + + +class TqdmCallback(keras.callbacks.Callback): + """Keras callback for epoch and batch progress.""" + @staticmethod + def bar2callback(bar, pop=None, delta=(lambda logs: 1)): + def callback(_, logs=None): + n = delta(logs) + if logs: + if pop: + logs = copy(logs) + [logs.pop(i, 0) for i in pop] + bar.set_postfix(logs, refresh=False) + bar.update(n) + + return callback + + def __init__(self, epochs=None, data_size=None, batch_size=None, verbose=1, + tqdm_class=tqdm_auto, **tqdm_kwargs): + """ + Parameters + ---------- + epochs : int, optional + data_size : int, optional + Number of training pairs. + batch_size : int, optional + Number of training pairs per batch. + verbose : int + 0: epoch, 1: batch (transient), 2: batch. [default: 1]. + Will be set to `0` unless both `data_size` and `batch_size` + are given. + tqdm_class : optional + `tqdm` class to use for bars [default: `tqdm.auto.tqdm`]. + tqdm_kwargs : optional + Any other arguments used for all bars. + """ + if tqdm_kwargs: + tqdm_class = partial(tqdm_class, **tqdm_kwargs) + self.tqdm_class = tqdm_class + self.epoch_bar = tqdm_class(total=epochs, unit='epoch') + self.on_epoch_end = self.bar2callback(self.epoch_bar) + if data_size and batch_size: + self.batches = batches = (data_size + batch_size - 1) // batch_size + else: + self.batches = batches = None + self.verbose = verbose + if verbose == 1: + self.batch_bar = tqdm_class(total=batches, unit='batch', leave=False) + self.on_batch_end = self.bar2callback( + self.batch_bar, pop=['batch', 'size'], + delta=lambda logs: logs.get('size', 1)) + + def on_train_begin(self, *_, **__): + params = self.params.get + auto_total = params('epochs', params('nb_epoch', None)) + if auto_total is not None and auto_total != self.epoch_bar.total: + self.epoch_bar.reset(total=auto_total) + + def on_epoch_begin(self, epoch, *_, **__): + if self.epoch_bar.n < epoch: + ebar = self.epoch_bar + ebar.n = ebar.last_print_n = ebar.initial = epoch + if self.verbose: + params = self.params.get + total = params('samples', params( + 'nb_sample', params('steps', None))) or self.batches + if self.verbose == 2: + if hasattr(self, 'batch_bar'): + self.batch_bar.close() + self.batch_bar = self.tqdm_class( + total=total, unit='batch', leave=True, + unit_scale=1 / (params('batch_size', 1) or 1)) + self.on_batch_end = self.bar2callback( + self.batch_bar, pop=['batch', 'size'], + delta=lambda logs: logs.get('size', 1)) + elif self.verbose == 1: + self.batch_bar.unit_scale = 1 / (params('batch_size', 1) or 1) + self.batch_bar.reset(total=total) + else: + raise KeyError('Unknown verbosity') + + def on_train_end(self, *_, **__): + if self.verbose: + self.batch_bar.close() + self.epoch_bar.close() + + def display(self): + """Displays in the current cell in Notebooks.""" + container = getattr(self.epoch_bar, 'container', None) + if container is None: + return + from .notebook import display + display(container) + batch_bar = getattr(self, 'batch_bar', None) + if batch_bar is not None: + display(batch_bar.container) + + @staticmethod + def _implements_train_batch_hooks(): + return True + + @staticmethod + def _implements_test_batch_hooks(): + return True + + @staticmethod + def _implements_predict_batch_hooks(): + return True diff --git a/lib/tqdm/notebook.py b/lib/tqdm/notebook.py new file mode 100644 index 0000000..d594b0f --- /dev/null +++ b/lib/tqdm/notebook.py @@ -0,0 +1,321 @@ +""" +IPython/Jupyter Notebook progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm.notebook import trange, tqdm +>>> for i in trange(10): +... ... +""" +# import compatibility functions and utilities +import re +import sys +from weakref import proxy + +# to inherit from the tqdm class +from .std import tqdm as std_tqdm + +if True: # pragma: no cover + # import IPython/Jupyter base widget and display utilities + IPY = 0 + try: # IPython 4.x + import ipywidgets + IPY = 4 + except ImportError: # IPython 3.x / 2.x + IPY = 32 + import warnings + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', message=".*The `IPython.html` package has been deprecated.*") + try: + import IPython.html.widgets as ipywidgets # NOQA: F401 + except ImportError: + pass + + try: # IPython 4.x / 3.x + if IPY == 32: + from IPython.html.widgets import HTML + from IPython.html.widgets import FloatProgress as IProgress + from IPython.html.widgets import HBox + IPY = 3 + else: + from ipywidgets import HTML + from ipywidgets import FloatProgress as IProgress + from ipywidgets import HBox + except ImportError: + try: # IPython 2.x + from IPython.html.widgets import HTML + from IPython.html.widgets import ContainerWidget as HBox + from IPython.html.widgets import FloatProgressWidget as IProgress + IPY = 2 + except ImportError: + IPY = 0 + IProgress = None + HBox = object + + try: + from IPython.display import display # , clear_output + except ImportError: + pass + + # HTML encoding + try: # Py3 + from html import escape + except ImportError: # Py2 + from cgi import escape + +__author__ = {"github.com/": ["lrq3000", "casperdcl", "alexanderkuk"]} +__all__ = ['tqdm_notebook', 'tnrange', 'tqdm', 'trange'] +WARN_NOIPYW = ("IProgress not found. Please update jupyter and ipywidgets." + " See https://ipywidgets.readthedocs.io/en/stable" + "/user_install.html") + + +class TqdmHBox(HBox): + """`ipywidgets.HBox` with a pretty representation""" + def _json_(self, pretty=None): + pbar = getattr(self, 'pbar', None) + if pbar is None: + return {} + d = pbar.format_dict + if pretty is not None: + d["ascii"] = not pretty + return d + + def __repr__(self, pretty=False): + pbar = getattr(self, 'pbar', None) + if pbar is None: + return super(TqdmHBox, self).__repr__() + return pbar.format_meter(**self._json_(pretty)) + + def _repr_pretty_(self, pp, *_, **__): + pp.text(self.__repr__(True)) + + +class tqdm_notebook(std_tqdm): + """ + Experimental IPython/Jupyter Notebook widget using tqdm! + """ + @staticmethod + def status_printer(_, total=None, desc=None, ncols=None): + """ + Manage the printing of an IPython/Jupyter Notebook progress bar widget. + """ + # Fallback to text bar if there's no total + # DEPRECATED: replaced with an 'info' style bar + # if not total: + # return super(tqdm_notebook, tqdm_notebook).status_printer(file) + + # fp = file + + # Prepare IPython progress bar + if IProgress is None: # #187 #451 #558 #872 + raise ImportError(WARN_NOIPYW) + if total: + pbar = IProgress(min=0, max=total) + else: # No total? Show info style bar with no progress tqdm status + pbar = IProgress(min=0, max=1) + pbar.value = 1 + pbar.bar_style = 'info' + if ncols is None: + pbar.layout.width = "20px" + + ltext = HTML() + rtext = HTML() + if desc: + ltext.value = desc + container = TqdmHBox(children=[ltext, pbar, rtext]) + # Prepare layout + if ncols is not None: # use default style of ipywidgets + # ncols could be 100, "100px", "100%" + ncols = str(ncols) # ipywidgets only accepts string + try: + if int(ncols) > 0: # isnumeric and positive + ncols += 'px' + except ValueError: + pass + pbar.layout.flex = '2' + container.layout.width = ncols + container.layout.display = 'inline-flex' + container.layout.flex_flow = 'row wrap' + + return container + + def display(self, msg=None, pos=None, + # additional signals + close=False, bar_style=None, check_delay=True): + # Note: contrary to native tqdm, msg='' does NOT clear bar + # goal is to keep all infos if error happens so user knows + # at which iteration the loop failed. + + # Clear previous output (really necessary?) + # clear_output(wait=1) + + if not msg and not close: + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + + ltext, pbar, rtext = self.container.children + pbar.value = self.n + + if msg: + # html escape special characters (like '&') + if '' in msg: + left, right = map(escape, re.split(r'\|?\|?', msg, 1)) + else: + left, right = '', escape(msg) + + # Update description + ltext.value = left + # never clear the bar (signal: msg='') + if right: + rtext.value = right + + # Change bar style + if bar_style: + # Hack-ish way to avoid the danger bar_style being overridden by + # success because the bar gets closed after the error... + if pbar.bar_style != 'danger' or bar_style != 'success': + pbar.bar_style = bar_style + + # Special signal to close the bar + if close and pbar.bar_style != 'danger': # hide only if no error + try: + self.container.close() + except AttributeError: + self.container.visible = False + self.container.layout.visibility = 'hidden' # IPYW>=8 + + if check_delay and self.delay > 0 and not self.displayed: + display(self.container) + self.displayed = True + + @property + def colour(self): + if hasattr(self, 'container'): + return self.container.children[-2].style.bar_color + + @colour.setter + def colour(self, bar_color): + if hasattr(self, 'container'): + self.container.children[-2].style.bar_color = bar_color + + def __init__(self, *args, **kwargs): + """ + Supports the usual `tqdm.tqdm` parameters as well as those listed below. + + Parameters + ---------- + display : Whether to call `display(self.container)` immediately + [default: True]. + """ + kwargs = kwargs.copy() + # Setup default output + file_kwarg = kwargs.get('file', sys.stderr) + if file_kwarg is sys.stderr or file_kwarg is None: + kwargs['file'] = sys.stdout # avoid the red block in IPython + + # Initialize parent class + avoid printing by using gui=True + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + colour = kwargs.pop('colour', None) + display_here = kwargs.pop('display', True) + super(tqdm_notebook, self).__init__(*args, **kwargs) + if self.disable or not kwargs['gui']: + self.disp = lambda *_, **__: None + return + + # Get bar width + self.ncols = '100%' if self.dynamic_ncols else kwargs.get("ncols", None) + + # Replace with IPython progress bar display (with correct total) + unit_scale = 1 if self.unit_scale is True else self.unit_scale or 1 + total = self.total * unit_scale if self.total else self.total + self.container = self.status_printer(self.fp, total, self.desc, self.ncols) + self.container.pbar = proxy(self) + self.displayed = False + if display_here and self.delay <= 0: + display(self.container) + self.displayed = True + self.disp = self.display + self.colour = colour + + # Print initial bar state + if not self.disable: + self.display(check_delay=False) + + def __iter__(self): + try: + it = super(tqdm_notebook, self).__iter__() + for obj in it: + # return super(tqdm...) will not catch exception + yield obj + # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt + except: # NOQA + self.disp(bar_style='danger') + raise + # NB: don't `finally: close()` + # since this could be a shared bar which the user will `reset()` + + def update(self, n=1): + try: + return super(tqdm_notebook, self).update(n=n) + # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt + except: # NOQA + # cannot catch KeyboardInterrupt when using manual tqdm + # as the interrupt will most likely happen on another statement + self.disp(bar_style='danger') + raise + # NB: don't `finally: close()` + # since this could be a shared bar which the user will `reset()` + + def close(self): + if self.disable: + return + super(tqdm_notebook, self).close() + # Try to detect if there was an error or KeyboardInterrupt + # in manual mode: if n < total, things probably got wrong + if self.total and self.n < self.total: + self.disp(bar_style='danger', check_delay=False) + else: + if self.leave: + self.disp(bar_style='success', check_delay=False) + else: + self.disp(close=True, check_delay=False) + + def clear(self, *_, **__): + pass + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with `leave=True`. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if self.disable: + return super(tqdm_notebook, self).reset(total=total) + _, pbar, _ = self.container.children + pbar.bar_style = '' + if total is not None: + pbar.max = total + if not self.total and self.ncols is None: # no longer unknown total + pbar.layout.width = None # reset width + return super(tqdm_notebook, self).reset(total=total) + + +def tnrange(*args, **kwargs): + """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" + return tqdm_notebook(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_notebook +trange = tnrange diff --git a/lib/tqdm/rich.py b/lib/tqdm/rich.py new file mode 100644 index 0000000..00e1ddf --- /dev/null +++ b/lib/tqdm/rich.py @@ -0,0 +1,150 @@ +""" +`rich.progress` decorator for iterators. + +Usage: +>>> from tqdm.rich import trange, tqdm +>>> for i in trange(10): +... ... +""" +from warnings import warn + +from rich.progress import ( + BarColumn, Progress, ProgressColumn, Text, TimeElapsedColumn, TimeRemainingColumn, filesize) + +from .std import TqdmExperimentalWarning +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tqdm_rich', 'trrange', 'tqdm', 'trange'] + + +class FractionColumn(ProgressColumn): + """Renders completed/total, e.g. '0.5/2.3 G'.""" + def __init__(self, unit_scale=False, unit_divisor=1000): + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + super().__init__() + + def render(self, task): + """Calculate common unit for completed and total.""" + completed = int(task.completed) + total = int(task.total) + if self.unit_scale: + unit, suffix = filesize.pick_unit_and_suffix( + total, + ["", "K", "M", "G", "T", "P", "E", "Z", "Y"], + self.unit_divisor, + ) + else: + unit, suffix = filesize.pick_unit_and_suffix(total, [""], 1) + precision = 0 if unit == 1 else 1 + return Text( + f"{completed/unit:,.{precision}f}/{total/unit:,.{precision}f} {suffix}", + style="progress.download") + + +class RateColumn(ProgressColumn): + """Renders human readable transfer speed.""" + def __init__(self, unit="", unit_scale=False, unit_divisor=1000): + self.unit = unit + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + super().__init__() + + def render(self, task): + """Show data transfer speed.""" + speed = task.speed + if speed is None: + return Text(f"? {self.unit}/s", style="progress.data.speed") + if self.unit_scale: + unit, suffix = filesize.pick_unit_and_suffix( + speed, + ["", "K", "M", "G", "T", "P", "E", "Z", "Y"], + self.unit_divisor, + ) + else: + unit, suffix = filesize.pick_unit_and_suffix(speed, [""], 1) + precision = 0 if unit == 1 else 1 + return Text(f"{speed/unit:,.{precision}f} {suffix}{self.unit}/s", + style="progress.data.speed") + + +class tqdm_rich(std_tqdm): # pragma: no cover + """Experimental rich.progress GUI version of tqdm!""" + # TODO: @classmethod: write()? + def __init__(self, *args, **kwargs): + """ + This class accepts the following parameters *in addition* to + the parameters accepted by `tqdm`. + + Parameters + ---------- + progress : tuple, optional + arguments for `rich.progress.Progress()`. + options : dict, optional + keyword arguments for `rich.progress.Progress()`. + """ + kwargs = kwargs.copy() + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + progress = kwargs.pop('progress', None) + options = kwargs.pop('options', {}).copy() + super(tqdm_rich, self).__init__(*args, **kwargs) + + if self.disable: + return + + warn("rich is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + d = self.format_dict + if progress is None: + progress = ( + "[progress.description]{task.description}" + "[progress.percentage]{task.percentage:>4.0f}%", + BarColumn(bar_width=None), + FractionColumn( + unit_scale=d['unit_scale'], unit_divisor=d['unit_divisor']), + "[", TimeElapsedColumn(), "<", TimeRemainingColumn(), + ",", RateColumn(unit=d['unit'], unit_scale=d['unit_scale'], + unit_divisor=d['unit_divisor']), "]" + ) + options.setdefault('transient', not self.leave) + self._prog = Progress(*progress, **options) + self._prog.__enter__() + self._task_id = self._prog.add_task(self.desc or "", **d) + + def close(self): + if self.disable: + return + super(tqdm_rich, self).close() + self._prog.__exit__(None, None, None) + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + if not hasattr(self, '_prog'): + return + self._prog.update(self._task_id, completed=self.n, description=self.desc) + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if hasattr(self, '_prog'): + self._prog.reset(total=total) + super(tqdm_rich, self).reset(total=total) + + +def trrange(*args, **kwargs): + """Shortcut for `tqdm.rich.tqdm(range(*args), **kwargs)`.""" + return tqdm_rich(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_rich +trange = trrange diff --git a/lib/tqdm/std.py b/lib/tqdm/std.py new file mode 100644 index 0000000..1163137 --- /dev/null +++ b/lib/tqdm/std.py @@ -0,0 +1,1521 @@ +""" +Customisable progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm import trange, tqdm +>>> for i in trange(10): +... ... +""" +import sys +from collections import OrderedDict, defaultdict +from contextlib import contextmanager +from datetime import datetime, timedelta +from numbers import Number +from time import time +from warnings import warn +from weakref import WeakSet + +from ._monitor import TMonitor +from .utils import ( + CallbackIOWrapper, Comparable, DisableOnWriteError, FormatReplace, SimpleTextIOWrapper, + _is_ascii, _screen_shape_wrapper, _supports_unicode, _term_move_up, disp_len, disp_trim) + +__author__ = "https://github.com/tqdm/tqdm#contributions" +__all__ = ['tqdm', 'trange', + 'TqdmTypeError', 'TqdmKeyError', 'TqdmWarning', + 'TqdmExperimentalWarning', 'TqdmDeprecationWarning', + 'TqdmMonitorWarning'] + + +class TqdmTypeError(TypeError): + pass + + +class TqdmKeyError(KeyError): + pass + + +class TqdmWarning(Warning): + """base class for all tqdm warnings. + + Used for non-external-code-breaking errors, such as garbled printing. + """ + def __init__(self, msg, fp_write=None, *a, **k): + if fp_write is not None: + fp_write("\n" + self.__class__.__name__ + ": " + str(msg).rstrip() + '\n') + else: + super(TqdmWarning, self).__init__(msg, *a, **k) + + +class TqdmExperimentalWarning(TqdmWarning, FutureWarning): + """beta feature, unstable API and behaviour""" + pass + + +class TqdmDeprecationWarning(TqdmWarning, DeprecationWarning): + # not suppressed if raised + pass + + +class TqdmMonitorWarning(TqdmWarning, RuntimeWarning): + """tqdm monitor errors which do not affect external functionality""" + pass + + +def TRLock(*args, **kwargs): + """threading RLock""" + try: + from threading import RLock + return RLock(*args, **kwargs) + except (ImportError, OSError): # pragma: no cover + pass + + +class TqdmDefaultWriteLock(object): + """ + Provide a default write lock for thread and multiprocessing safety. + Works only on platforms supporting `fork` (so Windows is excluded). + You must initialise a `tqdm` or `TqdmDefaultWriteLock` instance + before forking in order for the write lock to work. + On Windows, you need to supply the lock from the parent to the children as + an argument to joblib or the parallelism lib you use. + """ + # global thread lock so no setup required for multithreading. + # NB: Do not create multiprocessing lock as it sets the multiprocessing + # context, disallowing `spawn()`/`forkserver()` + th_lock = TRLock() + + def __init__(self): + # Create global parallelism locks to avoid racing issues with parallel + # bars works only if fork available (Linux/MacOSX, but not Windows) + cls = type(self) + root_lock = cls.th_lock + if root_lock is not None: + root_lock.acquire() + cls.create_mp_lock() + self.locks = [lk for lk in [cls.mp_lock, cls.th_lock] if lk is not None] + if root_lock is not None: + root_lock.release() + + def acquire(self, *a, **k): + for lock in self.locks: + lock.acquire(*a, **k) + + def release(self): + for lock in self.locks[::-1]: # Release in inverse order of acquisition + lock.release() + + def __enter__(self): + self.acquire() + + def __exit__(self, *exc): + self.release() + + @classmethod + def create_mp_lock(cls): + if not hasattr(cls, 'mp_lock'): + try: + from multiprocessing import RLock + cls.mp_lock = RLock() + except (ImportError, OSError): # pragma: no cover + cls.mp_lock = None + + @classmethod + def create_th_lock(cls): + assert hasattr(cls, 'th_lock') + warn("create_th_lock not needed anymore", TqdmDeprecationWarning, stacklevel=2) + + +class Bar(object): + """ + `str.format`-able bar with format specifiers: `[width][type]` + + - `width` + + unspecified (default): use `self.default_len` + + `int >= 0`: overrides `self.default_len` + + `int < 0`: subtract from `self.default_len` + - `type` + + `a`: ascii (`charset=self.ASCII` override) + + `u`: unicode (`charset=self.UTF` override) + + `b`: blank (`charset=" "` override) + """ + ASCII = " 123456789#" + UTF = u" " + u''.join(map(chr, range(0x258F, 0x2587, -1))) + BLANK = " " + COLOUR_RESET = '\x1b[0m' + COLOUR_RGB = '\x1b[38;2;%d;%d;%dm' + COLOURS = {'BLACK': '\x1b[30m', 'RED': '\x1b[31m', 'GREEN': '\x1b[32m', + 'YELLOW': '\x1b[33m', 'BLUE': '\x1b[34m', 'MAGENTA': '\x1b[35m', + 'CYAN': '\x1b[36m', 'WHITE': '\x1b[37m'} + + def __init__(self, frac, default_len=10, charset=UTF, colour=None): + if not 0 <= frac <= 1: + warn("clamping frac to range [0, 1]", TqdmWarning, stacklevel=2) + frac = max(0, min(1, frac)) + assert default_len > 0 + self.frac = frac + self.default_len = default_len + self.charset = charset + self.colour = colour + + @property + def colour(self): + return self._colour + + @colour.setter + def colour(self, value): + if not value: + self._colour = None + return + try: + if value.upper() in self.COLOURS: + self._colour = self.COLOURS[value.upper()] + elif value[0] == '#' and len(value) == 7: + self._colour = self.COLOUR_RGB % tuple( + int(i, 16) for i in (value[1:3], value[3:5], value[5:7])) + else: + raise KeyError + except (KeyError, AttributeError): + warn("Unknown colour (%s); valid choices: [hex (#00ff00), %s]" % ( + value, ", ".join(self.COLOURS)), + TqdmWarning, stacklevel=2) + self._colour = None + + def __format__(self, format_spec): + if format_spec: + _type = format_spec[-1].lower() + try: + charset = {'a': self.ASCII, 'u': self.UTF, 'b': self.BLANK}[_type] + except KeyError: + charset = self.charset + else: + format_spec = format_spec[:-1] + if format_spec: + N_BARS = int(format_spec) + if N_BARS < 0: + N_BARS += self.default_len + else: + N_BARS = self.default_len + else: + charset = self.charset + N_BARS = self.default_len + + nsyms = len(charset) - 1 + bar_length, frac_bar_length = divmod(int(self.frac * N_BARS * nsyms), nsyms) + + res = charset[-1] * bar_length + if bar_length < N_BARS: # whitespace padding + res = res + charset[frac_bar_length] + charset[0] * (N_BARS - bar_length - 1) + return self.colour + res + self.COLOUR_RESET if self.colour else res + + +class EMA(object): + """ + Exponential moving average: smoothing to give progressively lower + weights to older values. + + Parameters + ---------- + smoothing : float, optional + Smoothing factor in range [0, 1], [default: 0.3]. + Increase to give more weight to recent values. + Ranges from 0 (yields old value) to 1 (yields new value). + """ + def __init__(self, smoothing=0.3): + self.alpha = smoothing + self.last = 0 + self.calls = 0 + + def __call__(self, x=None): + """ + Parameters + ---------- + x : float + New value to include in EMA. + """ + beta = 1 - self.alpha + if x is not None: + self.last = self.alpha * x + beta * self.last + self.calls += 1 + return self.last / (1 - beta ** self.calls) if self.calls else self.last + + +class tqdm(Comparable): + """ + Decorate an iterable object, returning an iterator which acts exactly + like the original iterable, but prints a dynamically updating + progressbar every time a value is requested. + """ + + monitor_interval = 10 # set to 0 to disable the thread + monitor = None + _instances = WeakSet() + + @staticmethod + def format_sizeof(num, suffix='', divisor=1000): + """ + Formats a number (greater than unity) with SI Order of Magnitude + prefixes. + + Parameters + ---------- + num : float + Number ( >= 1) to format. + suffix : str, optional + Post-postfix [default: '']. + divisor : float, optional + Divisor between prefixes [default: 1000]. + + Returns + ------- + out : str + Number with Order of Magnitude SI unit postfix. + """ + for unit in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']: + if abs(num) < 999.5: + if abs(num) < 99.95: + if abs(num) < 9.995: + return '{0:1.2f}'.format(num) + unit + suffix + return '{0:2.1f}'.format(num) + unit + suffix + return '{0:3.0f}'.format(num) + unit + suffix + num /= divisor + return '{0:3.1f}Y'.format(num) + suffix + + @staticmethod + def format_interval(t): + """ + Formats a number of seconds as a clock time, [H:]MM:SS + + Parameters + ---------- + t : int + Number of seconds. + + Returns + ------- + out : str + [H:]MM:SS + """ + mins, s = divmod(int(t), 60) + h, m = divmod(mins, 60) + if h: + return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s) + else: + return '{0:02d}:{1:02d}'.format(m, s) + + @staticmethod + def format_num(n): + """ + Intelligent scientific notation (.3g). + + Parameters + ---------- + n : int or float or Numeric + A Number. + + Returns + ------- + out : str + Formatted number. + """ + f = '{0:.3g}'.format(n).replace('+0', '+').replace('-0', '-') + n = str(n) + return f if len(f) < len(n) else n + + @staticmethod + def status_printer(file): + """ + Manage the printing and in-place updating of a line of characters. + Note that if the string is longer than a line, then in-place + updating may not work (it will print a new line at each refresh). + """ + fp = file + fp_flush = getattr(fp, 'flush', lambda: None) # pragma: no cover + if fp in (sys.stderr, sys.stdout): + getattr(sys.stderr, 'flush', lambda: None)() + getattr(sys.stdout, 'flush', lambda: None)() + + def fp_write(s): + fp.write(str(s)) + fp_flush() + + last_len = [0] + + def print_status(s): + len_s = disp_len(s) + fp_write('\r' + s + (' ' * max(last_len[0] - len_s, 0))) + last_len[0] = len_s + + return print_status + + @staticmethod + def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False, unit='it', + unit_scale=False, rate=None, bar_format=None, postfix=None, + unit_divisor=1000, initial=0, colour=None, **extra_kwargs): + """ + Return a string-based progress bar given some parameters + + Parameters + ---------- + n : int or float + Number of finished iterations. + total : int or float + The expected total number of iterations. If meaningless (None), + only basic progress statistics are displayed (no ETA). + elapsed : float + Number of seconds passed since start. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes `{bar}` to stay within this bound + [default: None]. If `0`, will not print any bar (only stats). + The fallback is `{bar:10}`. + prefix : str, optional + Prefix message (included in total width) [default: '']. + Use as {desc} in bar_format string. + ascii : bool, optional or str, optional + If not set, use unicode (smooth blocks) to fill the meter + [default: False]. The fallback is to use ASCII characters + " 123456789#". + unit : str, optional + The iteration unit [default: 'it']. + unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be printed with an + appropriate SI metric prefix (k = 10^3, M = 10^6, etc.) + [default: False]. If any other non-zero number, will scale + `total` and `n`. + rate : float, optional + Manual override for iteration rate. + If [default: None], uses n/elapsed. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s, eta. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + postfix : *, optional + Similar to `prefix`, but placed at the end + (e.g. for additional stats). + Note: postfix is usually a string (not a dict) for this method, + and will if possible be set to postfix = ', ' + postfix. + However other types are supported (#382). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + initial : int or float, optional + The initial counter value [default: 0]. + colour : str, optional + Bar colour (e.g. 'green', '#00ff00'). + + Returns + ------- + out : Formatted meter and stats, ready to display. + """ + + # sanity check: total + if total and n >= (total + 0.5): # allow float imprecision (#849) + total = None + + # apply custom scale if necessary + if unit_scale and unit_scale not in (True, 1): + if total: + total *= unit_scale + n *= unit_scale + if rate: + rate *= unit_scale # by default rate = self.avg_dn / self.avg_dt + unit_scale = False + + elapsed_str = tqdm.format_interval(elapsed) + + # if unspecified, attempt to use rate = average speed + # (we allow manual override since predicting time is an arcane art) + if rate is None and elapsed: + rate = (n - initial) / elapsed + inv_rate = 1 / rate if rate else None + format_sizeof = tqdm.format_sizeof + rate_noinv_fmt = ((format_sizeof(rate) if unit_scale else + '{0:5.2f}'.format(rate)) if rate else '?') + unit + '/s' + rate_inv_fmt = ( + (format_sizeof(inv_rate) if unit_scale else '{0:5.2f}'.format(inv_rate)) + if inv_rate else '?') + 's/' + unit + rate_fmt = rate_inv_fmt if inv_rate and inv_rate > 1 else rate_noinv_fmt + + if unit_scale: + n_fmt = format_sizeof(n, divisor=unit_divisor) + total_fmt = format_sizeof(total, divisor=unit_divisor) if total is not None else '?' + else: + n_fmt = str(n) + total_fmt = str(total) if total is not None else '?' + + try: + postfix = ', ' + postfix if postfix else '' + except TypeError: + pass + + remaining = (total - n) / rate if rate and total else 0 + remaining_str = tqdm.format_interval(remaining) if rate else '?' + try: + eta_dt = (datetime.now() + timedelta(seconds=remaining) + if rate and total else datetime.utcfromtimestamp(0)) + except OverflowError: + eta_dt = datetime.max + + # format the stats displayed to the left and right sides of the bar + if prefix: + # old prefix setup work around + bool_prefix_colon_already = (prefix[-2:] == ": ") + l_bar = prefix if bool_prefix_colon_already else prefix + ": " + else: + l_bar = '' + + r_bar = f'| {n_fmt}/{total_fmt} [{elapsed_str}<{remaining_str}, {rate_fmt}{postfix}]' + + # Custom bar formatting + # Populate a dict with all available progress indicators + format_dict = { + # slight extension of self.format_dict + 'n': n, 'n_fmt': n_fmt, 'total': total, 'total_fmt': total_fmt, + 'elapsed': elapsed_str, 'elapsed_s': elapsed, + 'ncols': ncols, 'desc': prefix or '', 'unit': unit, + 'rate': inv_rate if inv_rate and inv_rate > 1 else rate, + 'rate_fmt': rate_fmt, 'rate_noinv': rate, + 'rate_noinv_fmt': rate_noinv_fmt, 'rate_inv': inv_rate, + 'rate_inv_fmt': rate_inv_fmt, + 'postfix': postfix, 'unit_divisor': unit_divisor, + 'colour': colour, + # plus more useful definitions + 'remaining': remaining_str, 'remaining_s': remaining, + 'l_bar': l_bar, 'r_bar': r_bar, 'eta': eta_dt, + **extra_kwargs} + + # total is known: we can predict some stats + if total: + # fractional and percentage progress + frac = n / total + percentage = frac * 100 + + l_bar += '{0:3.0f}%|'.format(percentage) + + if ncols == 0: + return l_bar[:-1] + r_bar[1:] + + format_dict.update(l_bar=l_bar) + if bar_format: + format_dict.update(percentage=percentage) + + # auto-remove colon for empty `{desc}` + if not prefix: + bar_format = bar_format.replace("{desc}: ", '') + else: + bar_format = "{l_bar}{bar}{r_bar}" + + full_bar = FormatReplace() + nobar = bar_format.format(bar=full_bar, **format_dict) + if not full_bar.format_called: + return nobar # no `{bar}`; nothing else to do + + # Formatting progress bar space available for bar's display + full_bar = Bar(frac, + max(1, ncols - disp_len(nobar)) if ncols else 10, + charset=Bar.ASCII if ascii is True else ascii or Bar.UTF, + colour=colour) + if not _is_ascii(full_bar.charset) and _is_ascii(bar_format): + bar_format = str(bar_format) + res = bar_format.format(bar=full_bar, **format_dict) + return disp_trim(res, ncols) if ncols else res + + elif bar_format: + # user-specified bar_format but no total + l_bar += '|' + format_dict.update(l_bar=l_bar, percentage=0) + full_bar = FormatReplace() + nobar = bar_format.format(bar=full_bar, **format_dict) + if not full_bar.format_called: + return nobar + full_bar = Bar(0, + max(1, ncols - disp_len(nobar)) if ncols else 10, + charset=Bar.BLANK, colour=colour) + res = bar_format.format(bar=full_bar, **format_dict) + return disp_trim(res, ncols) if ncols else res + else: + # no total: no progressbar, ETA, just progress stats + return (f'{(prefix + ": ") if prefix else ""}' + f'{n_fmt}{unit} [{elapsed_str}, {rate_fmt}{postfix}]') + + def __new__(cls, *_, **__): + instance = object.__new__(cls) + with cls.get_lock(): # also constructs lock if non-existent + cls._instances.add(instance) + # create monitoring thread + if cls.monitor_interval and (cls.monitor is None + or not cls.monitor.report()): + try: + cls.monitor = TMonitor(cls, cls.monitor_interval) + except Exception as e: # pragma: nocover + warn("tqdm:disabling monitor support" + " (monitor_interval = 0) due to:\n" + str(e), + TqdmMonitorWarning, stacklevel=2) + cls.monitor_interval = 0 + return instance + + @classmethod + def _get_free_pos(cls, instance=None): + """Skips specified instance.""" + positions = {abs(inst.pos) for inst in cls._instances + if inst is not instance and hasattr(inst, "pos")} + return min(set(range(len(positions) + 1)).difference(positions)) + + @classmethod + def _decr_instances(cls, instance): + """ + Remove from list and reposition another unfixed bar + to fill the new gap. + + This means that by default (where all nested bars are unfixed), + order is not maintained but screen flicker/blank space is minimised. + (tqdm<=4.44.1 moved ALL subsequent unfixed bars up.) + """ + with cls._lock: + try: + cls._instances.remove(instance) + except KeyError: + # if not instance.gui: # pragma: no cover + # raise + pass # py2: maybe magically removed already + # else: + if not instance.gui: + last = (instance.nrows or 20) - 1 + # find unfixed (`pos >= 0`) overflow (`pos >= nrows - 1`) + instances = list(filter( + lambda i: hasattr(i, "pos") and last <= i.pos, + cls._instances)) + # set first found to current `pos` + if instances: + inst = min(instances, key=lambda i: i.pos) + inst.clear(nolock=True) + inst.pos = abs(instance.pos) + + @classmethod + def write(cls, s, file=None, end="\n", nolock=False): + """Print a message via tqdm (without overlap with bars).""" + fp = file if file is not None else sys.stdout + with cls.external_write_mode(file=file, nolock=nolock): + # Write the message + fp.write(s) + fp.write(end) + + @classmethod + @contextmanager + def external_write_mode(cls, file=None, nolock=False): + """ + Disable tqdm within context and refresh tqdm when exits. + Useful when writing to standard output stream + """ + fp = file if file is not None else sys.stdout + + try: + if not nolock: + cls.get_lock().acquire() + # Clear all bars + inst_cleared = [] + for inst in getattr(cls, '_instances', []): + # Clear instance if in the target output file + # or if write output + tqdm output are both either + # sys.stdout or sys.stderr (because both are mixed in terminal) + if hasattr(inst, "start_t") and (inst.fp == fp or all( + f in (sys.stdout, sys.stderr) for f in (fp, inst.fp))): + inst.clear(nolock=True) + inst_cleared.append(inst) + yield + # Force refresh display of bars we cleared + for inst in inst_cleared: + inst.refresh(nolock=True) + finally: + if not nolock: + cls._lock.release() + + @classmethod + def set_lock(cls, lock): + """Set the global lock.""" + cls._lock = lock + + @classmethod + def get_lock(cls): + """Get the global lock. Construct it if it does not exist.""" + if not hasattr(cls, '_lock'): + cls._lock = TqdmDefaultWriteLock() + return cls._lock + + @classmethod + def pandas(cls, **tqdm_kwargs): + """ + Registers the current `tqdm` class with + pandas.core. + ( frame.DataFrame + | series.Series + | groupby.(generic.)DataFrameGroupBy + | groupby.(generic.)SeriesGroupBy + ).progress_apply + + A new instance will be created every time `progress_apply` is called, + and each instance will automatically `close()` upon completion. + + Parameters + ---------- + tqdm_kwargs : arguments for the tqdm instance + + Examples + -------- + >>> import pandas as pd + >>> import numpy as np + >>> from tqdm import tqdm + >>> from tqdm.gui import tqdm as tqdm_gui + >>> + >>> df = pd.DataFrame(np.random.randint(0, 100, (100000, 6))) + >>> tqdm.pandas(ncols=50) # can use tqdm_gui, optional kwargs, etc + >>> # Now you can use `progress_apply` instead of `apply` + >>> df.groupby(0).progress_apply(lambda x: x**2) + + References + ---------- + + """ + from warnings import catch_warnings, simplefilter + + from pandas.core.frame import DataFrame + from pandas.core.series import Series + try: + with catch_warnings(): + simplefilter("ignore", category=FutureWarning) + from pandas import Panel + except ImportError: # pandas>=1.2.0 + Panel = None + Rolling, Expanding = None, None + try: # pandas>=1.0.0 + from pandas.core.window.rolling import _Rolling_and_Expanding + except ImportError: + try: # pandas>=0.18.0 + from pandas.core.window import _Rolling_and_Expanding + except ImportError: # pandas>=1.2.0 + try: # pandas>=1.2.0 + from pandas.core.window.expanding import Expanding + from pandas.core.window.rolling import Rolling + _Rolling_and_Expanding = Rolling, Expanding + except ImportError: # pragma: no cover + _Rolling_and_Expanding = None + try: # pandas>=0.25.0 + from pandas.core.groupby.generic import SeriesGroupBy # , NDFrameGroupBy + from pandas.core.groupby.generic import DataFrameGroupBy + except ImportError: # pragma: no cover + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import DataFrameGroupBy, SeriesGroupBy + except ImportError: + from pandas.core.groupby import DataFrameGroupBy, SeriesGroupBy + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import GroupBy + except ImportError: # pragma: no cover + from pandas.core.groupby import GroupBy + + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import PanelGroupBy + except ImportError: + try: + from pandas.core.groupby import PanelGroupBy + except ImportError: # pandas>=0.25.0 + PanelGroupBy = None + + tqdm_kwargs = tqdm_kwargs.copy() + deprecated_t = [tqdm_kwargs.pop('deprecated_t', None)] + + def inner_generator(df_function='apply'): + def inner(df, func, *args, **kwargs): + """ + Parameters + ---------- + df : (DataFrame|Series)[GroupBy] + Data (may be grouped). + func : function + To be applied on the (grouped) data. + **kwargs : optional + Transmitted to `df.apply()`. + """ + + # Precompute total iterations + total = tqdm_kwargs.pop("total", getattr(df, 'ngroups', None)) + if total is None: # not grouped + if df_function == 'applymap': + total = df.size + elif isinstance(df, Series): + total = len(df) + elif (_Rolling_and_Expanding is None or + not isinstance(df, _Rolling_and_Expanding)): + # DataFrame or Panel + axis = kwargs.get('axis', 0) + if axis == 'index': + axis = 0 + elif axis == 'columns': + axis = 1 + # when axis=0, total is shape[axis1] + total = df.size // df.shape[axis] + + # Init bar + if deprecated_t[0] is not None: + t = deprecated_t[0] + deprecated_t[0] = None + else: + t = cls(total=total, **tqdm_kwargs) + + if len(args) > 0: + # *args intentionally not supported (see #244, #299) + TqdmDeprecationWarning( + "Except func, normal arguments are intentionally" + + " not supported by" + + " `(DataFrame|Series|GroupBy).progress_apply`." + + " Use keyword arguments instead.", + fp_write=getattr(t.fp, 'write', sys.stderr.write)) + + try: # pandas>=1.3.0 + from pandas.core.common import is_builtin_func + except ImportError: + is_builtin_func = df._is_builtin_func + try: + func = is_builtin_func(func) + except TypeError: + pass + + # Define bar updating wrapper + def wrapper(*args, **kwargs): + # update tbar correctly + # it seems `pandas apply` calls `func` twice + # on the first column/row to decide whether it can + # take a fast or slow code path; so stop when t.total==t.n + t.update(n=1 if not t.total or t.n < t.total else 0) + return func(*args, **kwargs) + + # Apply the provided function (in **kwargs) + # on the df using our wrapper (which provides bar updating) + try: + return getattr(df, df_function)(wrapper, **kwargs) + finally: + t.close() + + return inner + + # Monkeypatch pandas to provide easy methods + # Enable custom tqdm progress in pandas! + Series.progress_apply = inner_generator() + SeriesGroupBy.progress_apply = inner_generator() + Series.progress_map = inner_generator('map') + SeriesGroupBy.progress_map = inner_generator('map') + + DataFrame.progress_apply = inner_generator() + DataFrameGroupBy.progress_apply = inner_generator() + DataFrame.progress_applymap = inner_generator('applymap') + + if Panel is not None: + Panel.progress_apply = inner_generator() + if PanelGroupBy is not None: + PanelGroupBy.progress_apply = inner_generator() + + GroupBy.progress_apply = inner_generator() + GroupBy.progress_aggregate = inner_generator('aggregate') + GroupBy.progress_transform = inner_generator('transform') + + if Rolling is not None and Expanding is not None: + Rolling.progress_apply = inner_generator() + Expanding.progress_apply = inner_generator() + elif _Rolling_and_Expanding is not None: + _Rolling_and_Expanding.progress_apply = inner_generator() + + def __init__(self, iterable=None, desc=None, total=None, leave=True, file=None, + ncols=None, mininterval=0.1, maxinterval=10.0, miniters=None, + ascii=None, disable=False, unit='it', unit_scale=False, + dynamic_ncols=False, smoothing=0.3, bar_format=None, initial=0, + position=None, postfix=None, unit_divisor=1000, write_bytes=False, + lock_args=None, nrows=None, colour=None, delay=0, gui=False, + **kwargs): + """ + Parameters + ---------- + iterable : iterable, optional + Iterable to decorate with a progressbar. + Leave blank to manually manage the updates. + desc : str, optional + Prefix for the progressbar. + total : int or float, optional + The number of expected iterations. If unspecified, + len(iterable) is used if possible. If float("inf") or as a last + resort, only basic progress statistics are displayed + (no ETA, no progressbar). + If `gui` is True and this parameter needs subsequent updating, + specify an initial arbitrary large positive number, + e.g. 9e9. + leave : bool, optional + If [default: True], keeps all traces of the progressbar + upon termination of iteration. + If `None`, will leave only if `position` is `0`. + file : `io.TextIOWrapper` or `io.StringIO`, optional + Specifies where to output the progress messages + (default: sys.stderr). Uses `file.write(str)` and `file.flush()` + methods. For encoding, see `write_bytes`. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes the progressbar to stay within this bound. + If unspecified, attempts to use environment width. The + fallback is a meter width of 10 and no limit for the counter and + statistics. If 0, will not print any meter (only stats). + mininterval : float, optional + Minimum progress display update interval [default: 0.1] seconds. + maxinterval : float, optional + Maximum progress display update interval [default: 10] seconds. + Automatically adjusts `miniters` to correspond to `mininterval` + after long display update lag. Only works if `dynamic_miniters` + or monitor thread is enabled. + miniters : int or float, optional + Minimum progress display update interval, in iterations. + If 0 and `dynamic_miniters`, will automatically adjust to equal + `mininterval` (more CPU efficient, good for tight loops). + If > 0, will skip display of specified number of iterations. + Tweak this and `mininterval` to get very efficient loops. + If your progress is erratic with both fast and slow iterations + (network, skipping items, etc) you should set miniters=1. + ascii : bool or str, optional + If unspecified or False, use unicode (smooth blocks) to fill + the meter. The fallback is to use ASCII characters " 123456789#". + disable : bool, optional + Whether to disable the entire progressbar wrapper + [default: False]. If set to None, disable on non-TTY. + unit : str, optional + String that will be used to define the unit of each iteration + [default: it]. + unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be reduced/scaled + automatically and a metric prefix following the + International System of Units standard will be added + (kilo, mega, etc.) [default: False]. If any other non-zero + number, will scale `total` and `n`. + dynamic_ncols : bool, optional + If set, constantly alters `ncols` and `nrows` to the + environment (allowing for window resizes) [default: False]. + smoothing : float, optional + Exponential moving average smoothing factor for speed estimates + (ignored in GUI mode). Ranges from 0 (average speed) to 1 + (current/instantaneous speed) [default: 0.3]. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s, eta. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + initial : int or float, optional + The initial counter value. Useful when restarting a progress + bar [default: 0]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + position : int, optional + Specify the line offset to print this bar (starting from 0) + Automatic if unspecified. + Useful to manage multiple bars at once (eg, from threads). + postfix : dict or *, optional + Specify additional stats to display at the end of the bar. + Calls `set_postfix(**postfix)` if possible (dict). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + write_bytes : bool, optional + Whether to write bytes. If (default: False) will write unicode. + lock_args : tuple, optional + Passed to `refresh` for intermediate output + (initialisation, iterating, and updating). + nrows : int, optional + The screen height. If specified, hides nested bars outside this + bound. If unspecified, attempts to use environment height. + The fallback is 20. + colour : str, optional + Bar colour (e.g. 'green', '#00ff00'). + delay : float, optional + Don't display until [default: 0] seconds have elapsed. + gui : bool, optional + WARNING: internal parameter - do not use. + Use tqdm.gui.tqdm(...) instead. If set, will attempt to use + matplotlib animations for a graphical output [default: False]. + + Returns + ------- + out : decorated iterator. + """ + if file is None: + file = sys.stderr + + if write_bytes: + # Despite coercing unicode into bytes, py2 sys.std* streams + # should have bytes written to them. + file = SimpleTextIOWrapper( + file, encoding=getattr(file, 'encoding', None) or 'utf-8') + + file = DisableOnWriteError(file, tqdm_instance=self) + + if disable is None and hasattr(file, "isatty") and not file.isatty(): + disable = True + + if total is None and iterable is not None: + try: + total = len(iterable) + except (TypeError, AttributeError): + total = None + if total == float("inf"): + # Infinite iterations, behave same as unknown + total = None + + if disable: + self.iterable = iterable + self.disable = disable + with self._lock: + self.pos = self._get_free_pos(self) + self._instances.remove(self) + self.n = initial + self.total = total + self.leave = leave + return + + if kwargs: + self.disable = True + with self._lock: + self.pos = self._get_free_pos(self) + self._instances.remove(self) + raise ( + TqdmDeprecationWarning( + "`nested` is deprecated and automated.\n" + "Use `position` instead for manual control.\n", + fp_write=getattr(file, 'write', sys.stderr.write)) + if "nested" in kwargs else + TqdmKeyError("Unknown argument(s): " + str(kwargs))) + + # Preprocess the arguments + if ( + (ncols is None or nrows is None) and (file in (sys.stderr, sys.stdout)) + ) or dynamic_ncols: # pragma: no cover + if dynamic_ncols: + dynamic_ncols = _screen_shape_wrapper() + if dynamic_ncols: + ncols, nrows = dynamic_ncols(file) + else: + _dynamic_ncols = _screen_shape_wrapper() + if _dynamic_ncols: + _ncols, _nrows = _dynamic_ncols(file) + if ncols is None: + ncols = _ncols + if nrows is None: + nrows = _nrows + + if miniters is None: + miniters = 0 + dynamic_miniters = True + else: + dynamic_miniters = False + + if mininterval is None: + mininterval = 0 + + if maxinterval is None: + maxinterval = 0 + + if ascii is None: + ascii = not _supports_unicode(file) + + if bar_format and ascii is not True and not _is_ascii(ascii): + # Convert bar format into unicode since terminal uses unicode + bar_format = str(bar_format) + + if smoothing is None: + smoothing = 0 + + # Store the arguments + self.iterable = iterable + self.desc = desc or '' + self.total = total + self.leave = leave + self.fp = file + self.ncols = ncols + self.nrows = nrows + self.mininterval = mininterval + self.maxinterval = maxinterval + self.miniters = miniters + self.dynamic_miniters = dynamic_miniters + self.ascii = ascii + self.disable = disable + self.unit = unit + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + self.initial = initial + self.lock_args = lock_args + self.delay = delay + self.gui = gui + self.dynamic_ncols = dynamic_ncols + self.smoothing = smoothing + self._ema_dn = EMA(smoothing) + self._ema_dt = EMA(smoothing) + self._ema_miniters = EMA(smoothing) + self.bar_format = bar_format + self.postfix = None + self.colour = colour + self._time = time + if postfix: + try: + self.set_postfix(refresh=False, **postfix) + except TypeError: + self.postfix = postfix + + # Init the iterations counters + self.last_print_n = initial + self.n = initial + + # if nested, at initial sp() call we replace '\r' by '\n' to + # not overwrite the outer progress bar + with self._lock: + # mark fixed positions as negative + self.pos = self._get_free_pos(self) if position is None else -position + + if not gui: + # Initialize the screen printer + self.sp = self.status_printer(self.fp) + if delay <= 0: + self.refresh(lock_args=self.lock_args) + + # Init the time counter + self.last_print_t = self._time() + # NB: Avoid race conditions by setting start_t at the very end of init + self.start_t = self.last_print_t + + def __bool__(self): + if self.total is not None: + return self.total > 0 + if self.iterable is None: + raise TypeError('bool() undefined when iterable == total == None') + return bool(self.iterable) + + def __len__(self): + return ( + self.total if self.iterable is None + else self.iterable.shape[0] if hasattr(self.iterable, "shape") + else len(self.iterable) if hasattr(self.iterable, "__len__") + else self.iterable.__length_hint__() if hasattr(self.iterable, "__length_hint__") + else getattr(self, "total", None)) + + def __reversed__(self): + try: + orig = self.iterable + except AttributeError: + raise TypeError("'tqdm' object is not reversible") + else: + self.iterable = reversed(self.iterable) + return self.__iter__() + finally: + self.iterable = orig + + def __contains__(self, item): + contains = getattr(self.iterable, '__contains__', None) + return contains(item) if contains is not None else item in self.__iter__() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + try: + self.close() + except AttributeError: + # maybe eager thread cleanup upon external error + if (exc_type, exc_value, traceback) == (None, None, None): + raise + warn("AttributeError ignored", TqdmWarning, stacklevel=2) + + def __del__(self): + self.close() + + def __str__(self): + return self.format_meter(**self.format_dict) + + @property + def _comparable(self): + return abs(getattr(self, "pos", 1 << 31)) + + def __hash__(self): + return id(self) + + def __iter__(self): + """Backward-compatibility to use: for x in tqdm(iterable)""" + + # Inlining instance variables as locals (speed optimisation) + iterable = self.iterable + + # If the bar is disabled, then just walk the iterable + # (note: keep this check outside the loop for performance) + if self.disable: + for obj in iterable: + yield obj + return + + mininterval = self.mininterval + last_print_t = self.last_print_t + last_print_n = self.last_print_n + min_start_t = self.start_t + self.delay + n = self.n + time = self._time + + try: + for obj in iterable: + yield obj + # Update and possibly print the progressbar. + # Note: does not call self.update(1) for speed optimisation. + n += 1 + + if n - last_print_n >= self.miniters: + cur_t = time() + dt = cur_t - last_print_t + if dt >= mininterval and cur_t >= min_start_t: + self.update(n - last_print_n) + last_print_n = self.last_print_n + last_print_t = self.last_print_t + finally: + self.n = n + self.close() + + def update(self, n=1): + """ + Manually update the progress bar, useful for streams + such as reading files. + E.g.: + >>> t = tqdm(total=filesize) # Initialise + >>> for current_buffer in stream: + ... ... + ... t.update(len(current_buffer)) + >>> t.close() + The last line is highly recommended, but possibly not necessary if + `t.update()` will be called in such a way that `filesize` will be + exactly reached and printed. + + Parameters + ---------- + n : int or float, optional + Increment to add to the internal counter of iterations + [default: 1]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + + Returns + ------- + out : bool or None + True if a `display()` was triggered. + """ + if self.disable: + return + + if n < 0: + self.last_print_n += n # for auto-refresh logic to work + self.n += n + + # check counter first to reduce calls to time() + if self.n - self.last_print_n >= self.miniters: + cur_t = self._time() + dt = cur_t - self.last_print_t + if dt >= self.mininterval and cur_t >= self.start_t + self.delay: + cur_t = self._time() + dn = self.n - self.last_print_n # >= n + if self.smoothing and dt and dn: + # EMA (not just overall average) + self._ema_dn(dn) + self._ema_dt(dt) + self.refresh(lock_args=self.lock_args) + if self.dynamic_miniters: + # If no `miniters` was specified, adjust automatically to the + # maximum iteration rate seen so far between two prints. + # e.g.: After running `tqdm.update(5)`, subsequent + # calls to `tqdm.update()` will only cause an update after + # at least 5 more iterations. + if self.maxinterval and dt >= self.maxinterval: + self.miniters = dn * (self.mininterval or self.maxinterval) / dt + elif self.smoothing: + # EMA miniters update + self.miniters = self._ema_miniters( + dn * (self.mininterval / dt if self.mininterval and dt + else 1)) + else: + # max iters between two prints + self.miniters = max(self.miniters, dn) + + # Store old values for next call + self.last_print_n = self.n + self.last_print_t = cur_t + return True + + def close(self): + """Cleanup and (if leave=False) close the progressbar.""" + if self.disable: + return + + # Prevent multiple closures + self.disable = True + + # decrement instance pos and remove from internal set + pos = abs(self.pos) + self._decr_instances(self) + + if self.last_print_t < self.start_t + self.delay: + # haven't ever displayed; nothing to clear + return + + # GUI mode + if getattr(self, 'sp', None) is None: + return + + # annoyingly, _supports_unicode isn't good enough + def fp_write(s): + self.fp.write(str(s)) + + try: + fp_write('') + except ValueError as e: + if 'closed' in str(e): + return + raise # pragma: no cover + + leave = pos == 0 if self.leave is None else self.leave + + with self._lock: + if leave: + # stats for overall rate (no weighted average) + self._ema_dt = lambda: None + self.display(pos=0) + fp_write('\n') + else: + # clear previous display + if self.display(msg='', pos=pos) and not pos: + fp_write('\r') + + def clear(self, nolock=False): + """Clear current bar display.""" + if self.disable: + return + + if not nolock: + self._lock.acquire() + pos = abs(self.pos) + if pos < (self.nrows or 20): + self.moveto(pos) + self.sp('') + self.fp.write('\r') # place cursor back at the beginning of line + self.moveto(-pos) + if not nolock: + self._lock.release() + + def refresh(self, nolock=False, lock_args=None): + """ + Force refresh the display of this bar. + + Parameters + ---------- + nolock : bool, optional + If `True`, does not lock. + If [default: `False`]: calls `acquire()` on internal lock. + lock_args : tuple, optional + Passed to internal lock's `acquire()`. + If specified, will only `display()` if `acquire()` returns `True`. + """ + if self.disable: + return + + if not nolock: + if lock_args: + if not self._lock.acquire(*lock_args): + return False + else: + self._lock.acquire() + self.display() + if not nolock: + self._lock.release() + return True + + def unpause(self): + """Restart tqdm timer from last print time.""" + if self.disable: + return + cur_t = self._time() + self.start_t += cur_t - self.last_print_t + self.last_print_t = cur_t + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with `leave=True`. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + self.n = 0 + if total is not None: + self.total = total + if self.disable: + return + self.last_print_n = 0 + self.last_print_t = self.start_t = self._time() + self._ema_dn = EMA(self.smoothing) + self._ema_dt = EMA(self.smoothing) + self._ema_miniters = EMA(self.smoothing) + self.refresh() + + def set_description(self, desc=None, refresh=True): + """ + Set/modify description of the progress bar. + + Parameters + ---------- + desc : str, optional + refresh : bool, optional + Forces refresh [default: True]. + """ + self.desc = desc + ': ' if desc else '' + if refresh: + self.refresh() + + def set_description_str(self, desc=None, refresh=True): + """Set/modify description without ': ' appended.""" + self.desc = desc or '' + if refresh: + self.refresh() + + def set_postfix(self, ordered_dict=None, refresh=True, **kwargs): + """ + Set/modify postfix (additional stats) + with automatic formatting based on datatype. + + Parameters + ---------- + ordered_dict : dict or OrderedDict, optional + refresh : bool, optional + Forces refresh [default: True]. + kwargs : dict, optional + """ + # Sort in alphabetical order to be more deterministic + postfix = OrderedDict([] if ordered_dict is None else ordered_dict) + for key in sorted(kwargs.keys()): + postfix[key] = kwargs[key] + # Preprocess stats according to datatype + for key in postfix.keys(): + # Number: limit the length of the string + if isinstance(postfix[key], Number): + postfix[key] = self.format_num(postfix[key]) + # Else for any other type, try to get the string conversion + elif not isinstance(postfix[key], str): + postfix[key] = str(postfix[key]) + # Else if it's a string, don't need to preprocess anything + # Stitch together to get the final postfix + self.postfix = ', '.join(key + '=' + postfix[key].strip() + for key in postfix.keys()) + if refresh: + self.refresh() + + def set_postfix_str(self, s='', refresh=True): + """ + Postfix without dictionary expansion, similar to prefix handling. + """ + self.postfix = str(s) + if refresh: + self.refresh() + + def moveto(self, n): + # TODO: private method + self.fp.write('\n' * n + _term_move_up() * -n) + getattr(self.fp, 'flush', lambda: None)() + + @property + def format_dict(self): + """Public API for read-only member access.""" + if self.disable and not hasattr(self, 'unit'): + return defaultdict(lambda: None, { + 'n': self.n, 'total': self.total, 'elapsed': 0, 'unit': 'it'}) + if self.dynamic_ncols: + self.ncols, self.nrows = self.dynamic_ncols(self.fp) + return { + 'n': self.n, 'total': self.total, + 'elapsed': self._time() - self.start_t if hasattr(self, 'start_t') else 0, + 'ncols': self.ncols, 'nrows': self.nrows, 'prefix': self.desc, + 'ascii': self.ascii, 'unit': self.unit, 'unit_scale': self.unit_scale, + 'rate': self._ema_dn() / self._ema_dt() if self._ema_dt() else None, + 'bar_format': self.bar_format, 'postfix': self.postfix, + 'unit_divisor': self.unit_divisor, 'initial': self.initial, + 'colour': self.colour} + + def display(self, msg=None, pos=None): + """ + Use `self.sp` to display `msg` in the specified `pos`. + + Consider overloading this function when inheriting to use e.g.: + `self.some_frontend(**self.format_dict)` instead of `self.sp`. + + Parameters + ---------- + msg : str, optional. What to display (default: `repr(self)`). + pos : int, optional. Position to `moveto` + (default: `abs(self.pos)`). + """ + if pos is None: + pos = abs(self.pos) + + nrows = self.nrows or 20 + if pos >= nrows - 1: + if pos >= nrows: + return False + if msg or msg is None: # override at `nrows - 1` + msg = " ... (more hidden) ..." + + if not hasattr(self, "sp"): + raise TqdmDeprecationWarning( + "Please use `tqdm.gui.tqdm(...)`" + " instead of `tqdm(..., gui=True)`\n", + fp_write=getattr(self.fp, 'write', sys.stderr.write)) + + if pos: + self.moveto(pos) + self.sp(self.__str__() if msg is None else msg) + if pos: + self.moveto(-pos) + return True + + @classmethod + @contextmanager + def wrapattr(cls, stream, method, total=None, bytes=True, **tqdm_kwargs): + """ + stream : file-like object. + method : str, "read" or "write". The result of `read()` and + the first argument of `write()` should have a `len()`. + + >>> with tqdm.wrapattr(file_obj, "read", total=file_obj.size) as fobj: + ... while True: + ... chunk = fobj.read(chunk_size) + ... if not chunk: + ... break + """ + with cls(total=total, **tqdm_kwargs) as t: + if bytes: + t.unit = "B" + t.unit_scale = True + t.unit_divisor = 1024 + yield CallbackIOWrapper(t.update, stream, method) + + +def trange(*args, **kwargs): + """Shortcut for tqdm(range(*args), **kwargs).""" + return tqdm(range(*args), **kwargs) diff --git a/lib/tqdm/tk.py b/lib/tqdm/tk.py new file mode 100644 index 0000000..7852d80 --- /dev/null +++ b/lib/tqdm/tk.py @@ -0,0 +1,196 @@ +""" +Tkinter GUI progressbar decorator for iterators. + +Usage: +>>> from tqdm.tk import trange, tqdm +>>> for i in trange(10): +... ... +""" +import re +import sys +import tkinter +import tkinter.ttk as ttk +from warnings import warn + +from .std import TqdmExperimentalWarning, TqdmWarning +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["richardsheridan", "casperdcl"]} +__all__ = ['tqdm_tk', 'ttkrange', 'tqdm', 'trange'] + + +class tqdm_tk(std_tqdm): # pragma: no cover + """ + Experimental Tkinter GUI version of tqdm! + + Note: Window interactivity suffers if `tqdm_tk` is not running within + a Tkinter mainloop and values are generated infrequently. In this case, + consider calling `tqdm_tk.refresh()` frequently in the Tk thread. + """ + + # TODO: @classmethod: write()? + + def __init__(self, *args, **kwargs): + """ + This class accepts the following parameters *in addition* to + the parameters accepted by `tqdm`. + + Parameters + ---------- + grab : bool, optional + Grab the input across all windows of the process. + tk_parent : `tkinter.Wm`, optional + Parent Tk window. + cancel_callback : Callable, optional + Create a cancel button and set `cancel_callback` to be called + when the cancel or window close button is clicked. + """ + kwargs = kwargs.copy() + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + self._warn_leave = 'leave' in kwargs + grab = kwargs.pop('grab', False) + tk_parent = kwargs.pop('tk_parent', None) + self._cancel_callback = kwargs.pop('cancel_callback', None) + super(tqdm_tk, self).__init__(*args, **kwargs) + + if self.disable: + return + + if tk_parent is None: # Discover parent widget + try: + tk_parent = tkinter._default_root + except AttributeError: + raise AttributeError( + "`tk_parent` required when using `tkinter.NoDefaultRoot()`") + if tk_parent is None: # use new default root window as display + self._tk_window = tkinter.Tk() + else: # some other windows already exist + self._tk_window = tkinter.Toplevel() + else: + self._tk_window = tkinter.Toplevel(tk_parent) + + warn("GUI is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + self._tk_dispatching = self._tk_dispatching_helper() + + self._tk_window.protocol("WM_DELETE_WINDOW", self.cancel) + self._tk_window.wm_title(self.desc) + self._tk_window.wm_attributes("-topmost", 1) + self._tk_window.after(0, lambda: self._tk_window.wm_attributes("-topmost", 0)) + self._tk_n_var = tkinter.DoubleVar(self._tk_window, value=0) + self._tk_text_var = tkinter.StringVar(self._tk_window) + pbar_frame = ttk.Frame(self._tk_window, padding=5) + pbar_frame.pack() + _tk_label = ttk.Label(pbar_frame, textvariable=self._tk_text_var, + wraplength=600, anchor="center", justify="center") + _tk_label.pack() + self._tk_pbar = ttk.Progressbar( + pbar_frame, variable=self._tk_n_var, length=450) + if self.total is not None: + self._tk_pbar.configure(maximum=self.total) + else: + self._tk_pbar.configure(mode="indeterminate") + self._tk_pbar.pack() + if self._cancel_callback is not None: + _tk_button = ttk.Button(pbar_frame, text="Cancel", command=self.cancel) + _tk_button.pack() + if grab: + self._tk_window.grab_set() + + def close(self): + if self.disable: + return + + self.disable = True + + with self.get_lock(): + self._instances.remove(self) + + def _close(): + self._tk_window.after('idle', self._tk_window.destroy) + if not self._tk_dispatching: + self._tk_window.update() + + self._tk_window.protocol("WM_DELETE_WINDOW", _close) + + # if leave is set but we are self-dispatching, the left window is + # totally unresponsive unless the user manually dispatches + if not self.leave: + _close() + elif not self._tk_dispatching: + if self._warn_leave: + warn("leave flag ignored if not in tkinter mainloop", + TqdmWarning, stacklevel=2) + _close() + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + self._tk_n_var.set(self.n) + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + if '' in msg: + msg = "".join(re.split(r'\|?\|?', msg, 1)) + self._tk_text_var.set(msg) + if not self._tk_dispatching: + self._tk_window.update() + + def set_description(self, desc=None, refresh=True): + self.set_description_str(desc, refresh) + + def set_description_str(self, desc=None, refresh=True): + self.desc = desc + if not self.disable: + self._tk_window.wm_title(desc) + if refresh and not self._tk_dispatching: + self._tk_window.update() + + def cancel(self): + """ + `cancel_callback()` followed by `close()` + when close/cancel buttons clicked. + """ + if self._cancel_callback is not None: + self._cancel_callback() + self.close() + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if hasattr(self, '_tk_pbar'): + if total is None: + self._tk_pbar.configure(maximum=100, mode="indeterminate") + else: + self._tk_pbar.configure(maximum=total, mode="determinate") + super(tqdm_tk, self).reset(total=total) + + @staticmethod + def _tk_dispatching_helper(): + """determine if Tkinter mainloop is dispatching events""" + codes = {tkinter.mainloop.__code__, tkinter.Misc.mainloop.__code__} + for frame in sys._current_frames().values(): + while frame: + if frame.f_code in codes: + return True + frame = frame.f_back + return False + + +def ttkrange(*args, **kwargs): + """Shortcut for `tqdm.tk.tqdm(range(*args), **kwargs)`.""" + return tqdm_tk(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_tk +trange = ttkrange diff --git a/lib/tqdm/tqdm.1 b/lib/tqdm/tqdm.1 new file mode 100644 index 0000000..0533198 --- /dev/null +++ b/lib/tqdm/tqdm.1 @@ -0,0 +1,316 @@ +.\" Automatically generated by Pandoc 1.19.2 +.\" +.TH "TQDM" "1" "2015\-2021" "tqdm User Manuals" "" +.hy +.SH NAME +.PP +tqdm \- fast, extensible progress bar for Python and CLI +.SH SYNOPSIS +.PP +tqdm [\f[I]options\f[]] +.SH DESCRIPTION +.PP +See . +Can be used as a pipe: +.IP +.nf +\f[C] +$\ #\ count\ lines\ of\ code +$\ cat\ *.py\ |\ tqdm\ |\ wc\ \-l +327it\ [00:00,\ 981773.38it/s] +327 + +$\ #\ find\ all\ files +$\ find\ .\ \-name\ "*.py"\ |\ tqdm\ |\ wc\ \-l +432it\ [00:00,\ 833842.30it/s] +432 + +#\ ...\ and\ more\ info +$\ find\ .\ \-name\ \[aq]*.py\[aq]\ \-exec\ wc\ \-l\ \\{}\ \\;\ \\ +\ \ |\ tqdm\ \-\-total\ 432\ \-\-unit\ files\ \-\-desc\ counting\ \\ +\ \ |\ awk\ \[aq]{\ sum\ +=\ $1\ };\ END\ {\ print\ sum\ }\[aq] +counting:\ 100%|█████████|\ 432/432\ [00:00<00:00,\ 794361.83files/s] +131998 +\f[] +.fi +.SH OPTIONS +.TP +.B \-h, \-\-help +Print this help and exit. +.RS +.RE +.TP +.B \-v, \-\-version +Print version and exit. +.RS +.RE +.TP +.B \-\-desc=\f[I]desc\f[] +str, optional. +Prefix for the progressbar. +.RS +.RE +.TP +.B \-\-total=\f[I]total\f[] +int or float, optional. +The number of expected iterations. +If unspecified, len(iterable) is used if possible. +If float("inf") or as a last resort, only basic progress statistics are +displayed (no ETA, no progressbar). +If \f[C]gui\f[] is True and this parameter needs subsequent updating, +specify an initial arbitrary large positive number, e.g. +9e9. +.RS +.RE +.TP +.B \-\-leave +bool, optional. +If [default: True], keeps all traces of the progressbar upon termination +of iteration. +If \f[C]None\f[], will leave only if \f[C]position\f[] is \f[C]0\f[]. +.RS +.RE +.TP +.B \-\-ncols=\f[I]ncols\f[] +int, optional. +The width of the entire output message. +If specified, dynamically resizes the progressbar to stay within this +bound. +If unspecified, attempts to use environment width. +The fallback is a meter width of 10 and no limit for the counter and +statistics. +If 0, will not print any meter (only stats). +.RS +.RE +.TP +.B \-\-mininterval=\f[I]mininterval\f[] +float, optional. +Minimum progress display update interval [default: 0.1] seconds. +.RS +.RE +.TP +.B \-\-maxinterval=\f[I]maxinterval\f[] +float, optional. +Maximum progress display update interval [default: 10] seconds. +Automatically adjusts \f[C]miniters\f[] to correspond to +\f[C]mininterval\f[] after long display update lag. +Only works if \f[C]dynamic_miniters\f[] or monitor thread is enabled. +.RS +.RE +.TP +.B \-\-miniters=\f[I]miniters\f[] +int or float, optional. +Minimum progress display update interval, in iterations. +If 0 and \f[C]dynamic_miniters\f[], will automatically adjust to equal +\f[C]mininterval\f[] (more CPU efficient, good for tight loops). +If > 0, will skip display of specified number of iterations. +Tweak this and \f[C]mininterval\f[] to get very efficient loops. +If your progress is erratic with both fast and slow iterations (network, +skipping items, etc) you should set miniters=1. +.RS +.RE +.TP +.B \-\-ascii=\f[I]ascii\f[] +bool or str, optional. +If unspecified or False, use unicode (smooth blocks) to fill the meter. +The fallback is to use ASCII characters " 123456789#". +.RS +.RE +.TP +.B \-\-disable +bool, optional. +Whether to disable the entire progressbar wrapper [default: False]. +If set to None, disable on non\-TTY. +.RS +.RE +.TP +.B \-\-unit=\f[I]unit\f[] +str, optional. +String that will be used to define the unit of each iteration [default: +it]. +.RS +.RE +.TP +.B \-\-unit\-scale=\f[I]unit_scale\f[] +bool or int or float, optional. +If 1 or True, the number of iterations will be reduced/scaled +automatically and a metric prefix following the International System of +Units standard will be added (kilo, mega, etc.) [default: False]. +If any other non\-zero number, will scale \f[C]total\f[] and \f[C]n\f[]. +.RS +.RE +.TP +.B \-\-dynamic\-ncols +bool, optional. +If set, constantly alters \f[C]ncols\f[] and \f[C]nrows\f[] to the +environment (allowing for window resizes) [default: False]. +.RS +.RE +.TP +.B \-\-smoothing=\f[I]smoothing\f[] +float, optional. +Exponential moving average smoothing factor for speed estimates (ignored +in GUI mode). +Ranges from 0 (average speed) to 1 (current/instantaneous speed) +[default: 0.3]. +.RS +.RE +.TP +.B \-\-bar\-format=\f[I]bar_format\f[] +str, optional. +Specify a custom bar string formatting. +May impact performance. +[default: \[aq]{l_bar}{bar}{r_bar}\[aq]], where l_bar=\[aq]{desc}: +{percentage:3.0f}%|\[aq] and r_bar=\[aq]| {n_fmt}/{total_fmt} +[{elapsed}<{remaining}, \[aq] \[aq]{rate_fmt}{postfix}]\[aq] Possible +vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, percentage, +elapsed, elapsed_s, ncols, nrows, desc, unit, rate, rate_fmt, +rate_noinv, rate_noinv_fmt, rate_inv, rate_inv_fmt, postfix, +unit_divisor, remaining, remaining_s, eta. +Note that a trailing ": " is automatically removed after {desc} if the +latter is empty. +.RS +.RE +.TP +.B \-\-initial=\f[I]initial\f[] +int or float, optional. +The initial counter value. +Useful when restarting a progress bar [default: 0]. +If using float, consider specifying \f[C]{n:.3f}\f[] or similar in +\f[C]bar_format\f[], or specifying \f[C]unit_scale\f[]. +.RS +.RE +.TP +.B \-\-position=\f[I]position\f[] +int, optional. +Specify the line offset to print this bar (starting from 0) Automatic if +unspecified. +Useful to manage multiple bars at once (eg, from threads). +.RS +.RE +.TP +.B \-\-postfix=\f[I]postfix\f[] +dict or *, optional. +Specify additional stats to display at the end of the bar. +Calls \f[C]set_postfix(**postfix)\f[] if possible (dict). +.RS +.RE +.TP +.B \-\-unit\-divisor=\f[I]unit_divisor\f[] +float, optional. +[default: 1000], ignored unless \f[C]unit_scale\f[] is True. +.RS +.RE +.TP +.B \-\-write\-bytes +bool, optional. +If (default: None) and \f[C]file\f[] is unspecified, bytes will be +written in Python 2. +If \f[C]True\f[] will also write bytes. +In all other cases will default to unicode. +.RS +.RE +.TP +.B \-\-lock\-args=\f[I]lock_args\f[] +tuple, optional. +Passed to \f[C]refresh\f[] for intermediate output (initialisation, +iterating, and updating). +.RS +.RE +.TP +.B \-\-nrows=\f[I]nrows\f[] +int, optional. +The screen height. +If specified, hides nested bars outside this bound. +If unspecified, attempts to use environment height. +The fallback is 20. +.RS +.RE +.TP +.B \-\-colour=\f[I]colour\f[] +str, optional. +Bar colour (e.g. +\[aq]green\[aq], \[aq]#00ff00\[aq]). +.RS +.RE +.TP +.B \-\-delay=\f[I]delay\f[] +float, optional. +Don\[aq]t display until [default: 0] seconds have elapsed. +.RS +.RE +.TP +.B \-\-delim=\f[I]delim\f[] +chr, optional. +Delimiting character [default: \[aq]\\n\[aq]]. +Use \[aq]\\0\[aq] for null. +N.B.: on Windows systems, Python converts \[aq]\\n\[aq] to +\[aq]\\r\\n\[aq]. +.RS +.RE +.TP +.B \-\-buf\-size=\f[I]buf_size\f[] +int, optional. +String buffer size in bytes [default: 256] used when \f[C]delim\f[] is +specified. +.RS +.RE +.TP +.B \-\-bytes +bool, optional. +If true, will count bytes, ignore \f[C]delim\f[], and default +\f[C]unit_scale\f[] to True, \f[C]unit_divisor\f[] to 1024, and +\f[C]unit\f[] to \[aq]B\[aq]. +.RS +.RE +.TP +.B \-\-tee +bool, optional. +If true, passes \f[C]stdin\f[] to both \f[C]stderr\f[] and +\f[C]stdout\f[]. +.RS +.RE +.TP +.B \-\-update +bool, optional. +If true, will treat input as newly elapsed iterations, i.e. +numbers to pass to \f[C]update()\f[]. +Note that this is slow (~2e5 it/s) since every input must be decoded as +a number. +.RS +.RE +.TP +.B \-\-update\-to +bool, optional. +If true, will treat input as total elapsed iterations, i.e. +numbers to assign to \f[C]self.n\f[]. +Note that this is slow (~2e5 it/s) since every input must be decoded as +a number. +.RS +.RE +.TP +.B \-\-null +bool, optional. +If true, will discard input (no stdout). +.RS +.RE +.TP +.B \-\-manpath=\f[I]manpath\f[] +str, optional. +Directory in which to install tqdm man pages. +.RS +.RE +.TP +.B \-\-comppath=\f[I]comppath\f[] +str, optional. +Directory in which to place tqdm completion. +.RS +.RE +.TP +.B \-\-log=\f[I]log\f[] +str, optional. +CRITICAL|FATAL|ERROR|WARN(ING)|[default: \[aq]INFO\[aq]]|DEBUG|NOTSET. +.RS +.RE +.SH AUTHORS +tqdm developers . diff --git a/lib/tqdm/utils.py b/lib/tqdm/utils.py new file mode 100644 index 0000000..c5d3a6e --- /dev/null +++ b/lib/tqdm/utils.py @@ -0,0 +1,330 @@ +""" +General helpers required for `tqdm.std`. +""" +import os +import re +import sys +from functools import wraps +# TODO consider using wcswidth third-party package for 0-width characters +from unicodedata import east_asian_width +from warnings import warn +from weakref import proxy + +_range, _unich, _unicode, _basestring = range, chr, str, str +CUR_OS = sys.platform +IS_WIN = any(CUR_OS.startswith(i) for i in ['win32', 'cygwin']) +IS_NIX = any(CUR_OS.startswith(i) for i in ['aix', 'linux', 'darwin']) +RE_ANSI = re.compile(r"\x1b\[[;\d]*[A-Za-z]") + +try: + if IS_WIN: + import colorama + else: + raise ImportError +except ImportError: + colorama = None +else: + try: + colorama.init(strip=False) + except TypeError: + colorama.init() + + +class FormatReplace(object): + """ + >>> a = FormatReplace('something') + >>> "{:5d}".format(a) + 'something' + """ # NOQA: P102 + def __init__(self, replace=''): + self.replace = replace + self.format_called = 0 + + def __format__(self, _): + self.format_called += 1 + return self.replace + + +class Comparable(object): + """Assumes child has self._comparable attr/@property""" + def __lt__(self, other): + return self._comparable < other._comparable + + def __le__(self, other): + return (self < other) or (self == other) + + def __eq__(self, other): + return self._comparable == other._comparable + + def __ne__(self, other): + return not self == other + + def __gt__(self, other): + return not self <= other + + def __ge__(self, other): + return not self < other + + +class ObjectWrapper(object): + def __getattr__(self, name): + return getattr(self._wrapped, name) + + def __setattr__(self, name, value): + return setattr(self._wrapped, name, value) + + def wrapper_getattr(self, name): + """Actual `self.getattr` rather than self._wrapped.getattr""" + try: + return object.__getattr__(self, name) + except AttributeError: # py2 + return getattr(self, name) + + def wrapper_setattr(self, name, value): + """Actual `self.setattr` rather than self._wrapped.setattr""" + return object.__setattr__(self, name, value) + + def __init__(self, wrapped): + """ + Thin wrapper around a given object + """ + self.wrapper_setattr('_wrapped', wrapped) + + +class SimpleTextIOWrapper(ObjectWrapper): + """ + Change only `.write()` of the wrapped object by encoding the passed + value and passing the result to the wrapped object's `.write()` method. + """ + # pylint: disable=too-few-public-methods + def __init__(self, wrapped, encoding): + super(SimpleTextIOWrapper, self).__init__(wrapped) + self.wrapper_setattr('encoding', encoding) + + def write(self, s): + """ + Encode `s` and pass to the wrapped object's `.write()` method. + """ + return self._wrapped.write(s.encode(self.wrapper_getattr('encoding'))) + + def __eq__(self, other): + return self._wrapped == getattr(other, '_wrapped', other) + + +class DisableOnWriteError(ObjectWrapper): + """ + Disable the given `tqdm_instance` upon `write()` or `flush()` errors. + """ + @staticmethod + def disable_on_exception(tqdm_instance, func): + """ + Quietly set `tqdm_instance.miniters=inf` if `func` raises `errno=5`. + """ + tqdm_instance = proxy(tqdm_instance) + + def inner(*args, **kwargs): + try: + return func(*args, **kwargs) + except OSError as e: + if e.errno != 5: + raise + try: + tqdm_instance.miniters = float('inf') + except ReferenceError: + pass + except ValueError as e: + if 'closed' not in str(e): + raise + try: + tqdm_instance.miniters = float('inf') + except ReferenceError: + pass + return inner + + def __init__(self, wrapped, tqdm_instance): + super(DisableOnWriteError, self).__init__(wrapped) + if hasattr(wrapped, 'write'): + self.wrapper_setattr( + 'write', self.disable_on_exception(tqdm_instance, wrapped.write)) + if hasattr(wrapped, 'flush'): + self.wrapper_setattr( + 'flush', self.disable_on_exception(tqdm_instance, wrapped.flush)) + + def __eq__(self, other): + return self._wrapped == getattr(other, '_wrapped', other) + + +class CallbackIOWrapper(ObjectWrapper): + def __init__(self, callback, stream, method="read"): + """ + Wrap a given `file`-like object's `read()` or `write()` to report + lengths to the given `callback` + """ + super(CallbackIOWrapper, self).__init__(stream) + func = getattr(stream, method) + if method == "write": + @wraps(func) + def write(data, *args, **kwargs): + res = func(data, *args, **kwargs) + callback(len(data)) + return res + self.wrapper_setattr('write', write) + elif method == "read": + @wraps(func) + def read(*args, **kwargs): + data = func(*args, **kwargs) + callback(len(data)) + return data + self.wrapper_setattr('read', read) + else: + raise KeyError("Can only wrap read/write methods") + + +def _is_utf(encoding): + try: + u'\u2588\u2589'.encode(encoding) + except UnicodeEncodeError: + return False + except Exception: + try: + return encoding.lower().startswith('utf-') or ('U8' == encoding) + except Exception: + return False + else: + return True + + +def _supports_unicode(fp): + try: + return _is_utf(fp.encoding) + except AttributeError: + return False + + +def _is_ascii(s): + if isinstance(s, str): + for c in s: + if ord(c) > 255: + return False + return True + return _supports_unicode(s) + + +def _screen_shape_wrapper(): # pragma: no cover + """ + Return a function which returns console dimensions (width, height). + Supported: linux, osx, windows, cygwin. + """ + _screen_shape = None + if IS_WIN: + _screen_shape = _screen_shape_windows + if _screen_shape is None: + _screen_shape = _screen_shape_tput + if IS_NIX: + _screen_shape = _screen_shape_linux + return _screen_shape + + +def _screen_shape_windows(fp): # pragma: no cover + try: + import struct + from ctypes import create_string_buffer, windll + from sys import stdin, stdout + + io_handle = -12 # assume stderr + if fp == stdin: + io_handle = -10 + elif fp == stdout: + io_handle = -11 + + h = windll.kernel32.GetStdHandle(io_handle) + csbi = create_string_buffer(22) + res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) + if res: + (_bufx, _bufy, _curx, _cury, _wattr, left, top, right, bottom, + _maxx, _maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) + return right - left, bottom - top # +1 + except Exception: # nosec + pass + return None, None + + +def _screen_shape_tput(*_): # pragma: no cover + """cygwin xterm (windows)""" + try: + import shlex + from subprocess import check_call # nosec + return [int(check_call(shlex.split('tput ' + i))) - 1 + for i in ('cols', 'lines')] + except Exception: # nosec + pass + return None, None + + +def _screen_shape_linux(fp): # pragma: no cover + + try: + from array import array + from fcntl import ioctl + from termios import TIOCGWINSZ + except ImportError: + return None, None + else: + try: + rows, cols = array('h', ioctl(fp, TIOCGWINSZ, '\0' * 8))[:2] + return cols, rows + except Exception: + try: + return [int(os.environ[i]) - 1 for i in ("COLUMNS", "LINES")] + except (KeyError, ValueError): + return None, None + + +def _environ_cols_wrapper(): # pragma: no cover + """ + Return a function which returns console width. + Supported: linux, osx, windows, cygwin. + """ + warn("Use `_screen_shape_wrapper()(file)[0]` instead of" + " `_environ_cols_wrapper()(file)`", DeprecationWarning, stacklevel=2) + shape = _screen_shape_wrapper() + if not shape: + return None + + @wraps(shape) + def inner(fp): + return shape(fp)[0] + + return inner + + +def _term_move_up(): # pragma: no cover + return '' if (os.name == 'nt') and (colorama is None) else '\x1b[A' + + +def _text_width(s): + return sum(2 if east_asian_width(ch) in 'FW' else 1 for ch in str(s)) + + +def disp_len(data): + """ + Returns the real on-screen length of a string which may contain + ANSI control codes and wide chars. + """ + return _text_width(RE_ANSI.sub('', data)) + + +def disp_trim(data, length): + """ + Trim a string which may contain ANSI control characters. + """ + if len(data) == disp_len(data): + return data[:length] + + ansi_present = bool(RE_ANSI.search(data)) + while disp_len(data) > length: # carefully delete one char at a time + data = data[:-1] + if ansi_present and bool(RE_ANSI.search(data)): + # assume ANSI reset is required + return data if data.endswith("\033[0m") else data + "\033[0m" + return data diff --git a/lib/tqdm/version.py b/lib/tqdm/version.py new file mode 100644 index 0000000..11cbaea --- /dev/null +++ b/lib/tqdm/version.py @@ -0,0 +1,9 @@ +"""`tqdm` version detector. Precedence: installed dist, git, 'UNKNOWN'.""" +try: + from ._dist_ver import __version__ +except ImportError: + try: + from setuptools_scm import get_version + __version__ = get_version(root='..', relative_to=__file__) + except (ImportError, LookupError): + __version__ = "UNKNOWN" diff --git a/lib/urllib3-1.26.15.dist-info/INSTALLER b/lib/urllib3-1.26.15.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/urllib3-1.26.15.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/urllib3-1.26.15.dist-info/LICENSE.txt b/lib/urllib3-1.26.15.dist-info/LICENSE.txt new file mode 100644 index 0000000..429a176 --- /dev/null +++ b/lib/urllib3-1.26.15.dist-info/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2008-2020 Andrey Petrov and contributors (see CONTRIBUTORS.txt) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/urllib3-1.26.15.dist-info/METADATA b/lib/urllib3-1.26.15.dist-info/METADATA new file mode 100644 index 0000000..5d2a438 --- /dev/null +++ b/lib/urllib3-1.26.15.dist-info/METADATA @@ -0,0 +1,1472 @@ +Metadata-Version: 2.1 +Name: urllib3 +Version: 1.26.15 +Summary: HTTP library with thread-safe connection pooling, file post, and more. +Home-page: https://urllib3.readthedocs.io/ +Author: Andrey Petrov +Author-email: andrey.petrov@shazow.net +License: MIT +Project-URL: Documentation, https://urllib3.readthedocs.io/ +Project-URL: Code, https://github.com/urllib3/urllib3 +Project-URL: Issue tracker, https://github.com/urllib3/urllib3/issues +Keywords: urllib httplib threadsafe filepost http https ssl pooling +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.* +Description-Content-Type: text/x-rst +License-File: LICENSE.txt +Provides-Extra: brotli +Requires-Dist: brotlicffi (>=0.8.0) ; ((os_name != "nt" or python_version >= "3") and platform_python_implementation != "CPython") and extra == 'brotli' +Requires-Dist: brotli (>=1.0.9) ; ((os_name != "nt" or python_version >= "3") and platform_python_implementation == "CPython") and extra == 'brotli' +Requires-Dist: brotlipy (>=0.6.0) ; (os_name == "nt" and python_version < "3") and extra == 'brotli' +Provides-Extra: secure +Requires-Dist: pyOpenSSL (>=0.14) ; extra == 'secure' +Requires-Dist: cryptography (>=1.3.4) ; extra == 'secure' +Requires-Dist: idna (>=2.0.0) ; extra == 'secure' +Requires-Dist: certifi ; extra == 'secure' +Requires-Dist: urllib3-secure-extra ; extra == 'secure' +Requires-Dist: ipaddress ; (python_version == "2.7") and extra == 'secure' +Provides-Extra: socks +Requires-Dist: PySocks (!=1.5.7,<2.0,>=1.5.6) ; extra == 'socks' + + +urllib3 is a powerful, *user-friendly* HTTP client for Python. Much of the +Python ecosystem already uses urllib3 and you should too. +urllib3 brings many critical features that are missing from the Python +standard libraries: + +- Thread safety. +- Connection pooling. +- Client-side SSL/TLS verification. +- File uploads with multipart encoding. +- Helpers for retrying requests and dealing with HTTP redirects. +- Support for gzip, deflate, and brotli encoding. +- Proxy support for HTTP and SOCKS. +- 100% test coverage. + +urllib3 is powerful and easy to use: + +.. code-block:: python + + >>> import urllib3 + >>> http = urllib3.PoolManager() + >>> r = http.request('GET', 'http://httpbin.org/robots.txt') + >>> r.status + 200 + >>> r.data + 'User-agent: *\nDisallow: /deny\n' + + +Installing +---------- + +urllib3 can be installed with `pip `_:: + + $ python -m pip install urllib3 + +Alternatively, you can grab the latest source code from `GitHub `_:: + + $ git clone https://github.com/urllib3/urllib3.git + $ cd urllib3 + $ git checkout 1.26.x + $ pip install . + + +Documentation +------------- + +urllib3 has usage and reference documentation at `urllib3.readthedocs.io `_. + + +Contributing +------------ + +urllib3 happily accepts contributions. Please see our +`contributing documentation `_ +for some tips on getting started. + + +Security Disclosures +-------------------- + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure with maintainers. + + +Maintainers +----------- + +- `@sethmlarson `__ (Seth M. Larson) +- `@pquentin `__ (Quentin Pradet) +- `@theacodes `__ (Thea Flowers) +- `@haikuginger `__ (Jess Shapiro) +- `@lukasa `__ (Cory Benfield) +- `@sigmavirus24 `__ (Ian Stapleton Cordasco) +- `@shazow `__ (Andrey Petrov) + +👋 + + +Sponsorship +----------- + +If your company benefits from this library, please consider `sponsoring its +development `_. + + +For Enterprise +-------------- + +.. |tideliftlogo| image:: https://nedbatchelder.com/pix/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png + :width: 75 + :alt: Tidelift + +.. list-table:: + :widths: 10 100 + + * - |tideliftlogo| + - Professional support for urllib3 is available as part of the `Tidelift + Subscription`_. Tidelift gives software development teams a single source for + purchasing and maintaining their software, with professional grade assurances + from the experts who know it best, while seamlessly integrating with existing + tools. + +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-urllib3?utm_source=pypi-urllib3&utm_medium=referral&utm_campaign=readme + + +Changes +======= + +1.26.15 (2023-03-10) +-------------------- + +* Fix socket timeout value when ``HTTPConnection`` is reused (`#2645 `__) +* Remove "!" character from the unreserved characters in IPv6 Zone ID parsing + (`#2899 `__) +* Fix IDNA handling of '\x80' byte (`#2901 `__) + +1.26.14 (2023-01-11) +-------------------- + +* Fixed parsing of port 0 (zero) returning None, instead of 0. (`#2850 `__) +* Removed deprecated getheaders() calls in contrib module. + +1.26.13 (2022-11-23) +-------------------- + +* Deprecated the ``HTTPResponse.getheaders()`` and ``HTTPResponse.getheader()`` methods. +* Fixed an issue where parsing a URL with leading zeroes in the port would be rejected + even when the port number after removing the zeroes was valid. +* Fixed a deprecation warning when using cryptography v39.0.0. +* Removed the ``<4`` in the ``Requires-Python`` packaging metadata field. + + +1.26.12 (2022-08-22) +-------------------- + +* Deprecated the `urllib3[secure]` extra and the `urllib3.contrib.pyopenssl` module. + Both will be removed in v2.x. See this `GitHub issue `_ + for justification and info on how to migrate. + + +1.26.11 (2022-07-25) +-------------------- + +* Fixed an issue where reading more than 2 GiB in a call to ``HTTPResponse.read`` would + raise an ``OverflowError`` on Python 3.9 and earlier. + + +1.26.10 (2022-07-07) +-------------------- + +* Removed support for Python 3.5 +* Fixed an issue where a ``ProxyError`` recommending configuring the proxy as HTTP + instead of HTTPS could appear even when an HTTPS proxy wasn't configured. + + +1.26.9 (2022-03-16) +------------------- + +* Changed ``urllib3[brotli]`` extra to favor installing Brotli libraries that are still + receiving updates like ``brotli`` and ``brotlicffi`` instead of ``brotlipy``. + This change does not impact behavior of urllib3, only which dependencies are installed. +* Fixed a socket leaking when ``HTTPSConnection.connect()`` raises an exception. +* Fixed ``server_hostname`` being forwarded from ``PoolManager`` to ``HTTPConnectionPool`` + when requesting an HTTP URL. Should only be forwarded when requesting an HTTPS URL. + + +1.26.8 (2022-01-07) +------------------- + +* Added extra message to ``urllib3.exceptions.ProxyError`` when urllib3 detects that + a proxy is configured to use HTTPS but the proxy itself appears to only use HTTP. +* Added a mention of the size of the connection pool when discarding a connection due to the pool being full. +* Added explicit support for Python 3.11. +* Deprecated the ``Retry.MAX_BACKOFF`` class property in favor of ``Retry.DEFAULT_MAX_BACKOFF`` + to better match the rest of the default parameter names. ``Retry.MAX_BACKOFF`` is removed in v2.0. +* Changed location of the vendored ``ssl.match_hostname`` function from ``urllib3.packages.ssl_match_hostname`` + to ``urllib3.util.ssl_match_hostname`` to ensure Python 3.10+ compatibility after being repackaged + by downstream distributors. +* Fixed absolute imports, all imports are now relative. + + +1.26.7 (2021-09-22) +------------------- + +* Fixed a bug with HTTPS hostname verification involving IP addresses and lack + of SNI. (Issue #2400) +* Fixed a bug where IPv6 braces weren't stripped during certificate hostname + matching. (Issue #2240) + + +1.26.6 (2021-06-25) +------------------- + +* Deprecated the ``urllib3.contrib.ntlmpool`` module. urllib3 is not able to support + it properly due to `reasons listed in this issue `_. + If you are a user of this module please leave a comment. +* Changed ``HTTPConnection.request_chunked()`` to not erroneously emit multiple + ``Transfer-Encoding`` headers in the case that one is already specified. +* Fixed typo in deprecation message to recommend ``Retry.DEFAULT_ALLOWED_METHODS``. + + +1.26.5 (2021-05-26) +------------------- + +* Fixed deprecation warnings emitted in Python 3.10. +* Updated vendored ``six`` library to 1.16.0. +* Improved performance of URL parser when splitting + the authority component. + + +1.26.4 (2021-03-15) +------------------- + +* Changed behavior of the default ``SSLContext`` when connecting to HTTPS proxy + during HTTPS requests. The default ``SSLContext`` now sets ``check_hostname=True``. + + +1.26.3 (2021-01-26) +------------------- + +* Fixed bytes and string comparison issue with headers (Pull #2141) + +* Changed ``ProxySchemeUnknown`` error message to be + more actionable if the user supplies a proxy URL without + a scheme. (Pull #2107) + + +1.26.2 (2020-11-12) +------------------- + +* Fixed an issue where ``wrap_socket`` and ``CERT_REQUIRED`` wouldn't + be imported properly on Python 2.7.8 and earlier (Pull #2052) + + +1.26.1 (2020-11-11) +------------------- + +* Fixed an issue where two ``User-Agent`` headers would be sent if a + ``User-Agent`` header key is passed as ``bytes`` (Pull #2047) + + +1.26.0 (2020-11-10) +------------------- + +* **NOTE: urllib3 v2.0 will drop support for Python 2**. + `Read more in the v2.0 Roadmap `_. + +* Added support for HTTPS proxies contacting HTTPS servers (Pull #1923, Pull #1806) + +* Deprecated negotiating TLSv1 and TLSv1.1 by default. Users that + still wish to use TLS earlier than 1.2 without a deprecation warning + should opt-in explicitly by setting ``ssl_version=ssl.PROTOCOL_TLSv1_1`` (Pull #2002) + **Starting in urllib3 v2.0: Connections that receive a ``DeprecationWarning`` will fail** + +* Deprecated ``Retry`` options ``Retry.DEFAULT_METHOD_WHITELIST``, ``Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST`` + and ``Retry(method_whitelist=...)`` in favor of ``Retry.DEFAULT_ALLOWED_METHODS``, + ``Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT``, and ``Retry(allowed_methods=...)`` + (Pull #2000) **Starting in urllib3 v2.0: Deprecated options will be removed** + +* Added default ``User-Agent`` header to every request (Pull #1750) + +* Added ``urllib3.util.SKIP_HEADER`` for skipping ``User-Agent``, ``Accept-Encoding``, + and ``Host`` headers from being automatically emitted with requests (Pull #2018) + +* Collapse ``transfer-encoding: chunked`` request data and framing into + the same ``socket.send()`` call (Pull #1906) + +* Send ``http/1.1`` ALPN identifier with every TLS handshake by default (Pull #1894) + +* Properly terminate SecureTransport connections when CA verification fails (Pull #1977) + +* Don't emit an ``SNIMissingWarning`` when passing ``server_hostname=None`` + to SecureTransport (Pull #1903) + +* Disabled requesting TLSv1.2 session tickets as they weren't being used by urllib3 (Pull #1970) + +* Suppress ``BrokenPipeError`` when writing request body after the server + has closed the socket (Pull #1524) + +* Wrap ``ssl.SSLError`` that can be raised from reading a socket (e.g. "bad MAC") + into an ``urllib3.exceptions.SSLError`` (Pull #1939) + + +1.25.11 (2020-10-19) +-------------------- + +* Fix retry backoff time parsed from ``Retry-After`` header when given + in the HTTP date format. The HTTP date was parsed as the local timezone + rather than accounting for the timezone in the HTTP date (typically + UTC) (Pull #1932, Pull #1935, Pull #1938, Pull #1949) + +* Fix issue where an error would be raised when the ``SSLKEYLOGFILE`` + environment variable was set to the empty string. Now ``SSLContext.keylog_file`` + is not set in this situation (Pull #2016) + + +1.25.10 (2020-07-22) +-------------------- + +* Added support for ``SSLKEYLOGFILE`` environment variable for + logging TLS session keys with use with programs like + Wireshark for decrypting captured web traffic (Pull #1867) + +* Fixed loading of SecureTransport libraries on macOS Big Sur + due to the new dynamic linker cache (Pull #1905) + +* Collapse chunked request bodies data and framing into one + call to ``send()`` to reduce the number of TCP packets by 2-4x (Pull #1906) + +* Don't insert ``None`` into ``ConnectionPool`` if the pool + was empty when requesting a connection (Pull #1866) + +* Avoid ``hasattr`` call in ``BrotliDecoder.decompress()`` (Pull #1858) + + +1.25.9 (2020-04-16) +------------------- + +* Added ``InvalidProxyConfigurationWarning`` which is raised when + erroneously specifying an HTTPS proxy URL. urllib3 doesn't currently + support connecting to HTTPS proxies but will soon be able to + and we would like users to migrate properly without much breakage. + + See `this GitHub issue `_ + for more information on how to fix your proxy config. (Pull #1851) + +* Drain connection after ``PoolManager`` redirect (Pull #1817) + +* Ensure ``load_verify_locations`` raises ``SSLError`` for all backends (Pull #1812) + +* Rename ``VerifiedHTTPSConnection`` to ``HTTPSConnection`` (Pull #1805) + +* Allow the CA certificate data to be passed as a string (Pull #1804) + +* Raise ``ValueError`` if method contains control characters (Pull #1800) + +* Add ``__repr__`` to ``Timeout`` (Pull #1795) + + +1.25.8 (2020-01-20) +------------------- + +* Drop support for EOL Python 3.4 (Pull #1774) + +* Optimize _encode_invalid_chars (Pull #1787) + + +1.25.7 (2019-11-11) +------------------- + +* Preserve ``chunked`` parameter on retries (Pull #1715, Pull #1734) + +* Allow unset ``SERVER_SOFTWARE`` in App Engine (Pull #1704, Issue #1470) + +* Fix issue where URL fragment was sent within the request target. (Pull #1732) + +* Fix issue where an empty query section in a URL would fail to parse. (Pull #1732) + +* Remove TLS 1.3 support in SecureTransport due to Apple removing support (Pull #1703) + + +1.25.6 (2019-09-24) +------------------- + +* Fix issue where tilde (``~``) characters were incorrectly + percent-encoded in the path. (Pull #1692) + + +1.25.5 (2019-09-19) +------------------- + +* Add mitigation for BPO-37428 affecting Python <3.7.4 and OpenSSL 1.1.1+ which + caused certificate verification to be enabled when using ``cert_reqs=CERT_NONE``. + (Issue #1682) + + +1.25.4 (2019-09-19) +------------------- + +* Propagate Retry-After header settings to subsequent retries. (Pull #1607) + +* Fix edge case where Retry-After header was still respected even when + explicitly opted out of. (Pull #1607) + +* Remove dependency on ``rfc3986`` for URL parsing. + +* Fix issue where URLs containing invalid characters within ``Url.auth`` would + raise an exception instead of percent-encoding those characters. + +* Add support for ``HTTPResponse.auto_close = False`` which makes HTTP responses + work well with BufferedReaders and other ``io`` module features. (Pull #1652) + +* Percent-encode invalid characters in URL for ``HTTPConnectionPool.request()`` (Pull #1673) + + +1.25.3 (2019-05-23) +------------------- + +* Change ``HTTPSConnection`` to load system CA certificates + when ``ca_certs``, ``ca_cert_dir``, and ``ssl_context`` are + unspecified. (Pull #1608, Issue #1603) + +* Upgrade bundled rfc3986 to v1.3.2. (Pull #1609, Issue #1605) + + +1.25.2 (2019-04-28) +------------------- + +* Change ``is_ipaddress`` to not detect IPvFuture addresses. (Pull #1583) + +* Change ``parse_url`` to percent-encode invalid characters within the + path, query, and target components. (Pull #1586) + + +1.25.1 (2019-04-24) +------------------- + +* Add support for Google's ``Brotli`` package. (Pull #1572, Pull #1579) + +* Upgrade bundled rfc3986 to v1.3.1 (Pull #1578) + + +1.25 (2019-04-22) +----------------- + +* Require and validate certificates by default when using HTTPS (Pull #1507) + +* Upgraded ``urllib3.utils.parse_url()`` to be RFC 3986 compliant. (Pull #1487) + +* Added support for ``key_password`` for ``HTTPSConnectionPool`` to use + encrypted ``key_file`` without creating your own ``SSLContext`` object. (Pull #1489) + +* Add TLSv1.3 support to CPython, pyOpenSSL, and SecureTransport ``SSLContext`` + implementations. (Pull #1496) + +* Switched the default multipart header encoder from RFC 2231 to HTML 5 working draft. (Issue #303, Pull #1492) + +* Fixed issue where OpenSSL would block if an encrypted client private key was + given and no password was given. Instead an ``SSLError`` is raised. (Pull #1489) + +* Added support for Brotli content encoding. It is enabled automatically if + ``brotlipy`` package is installed which can be requested with + ``urllib3[brotli]`` extra. (Pull #1532) + +* Drop ciphers using DSS key exchange from default TLS cipher suites. + Improve default ciphers when using SecureTransport. (Pull #1496) + +* Implemented a more efficient ``HTTPResponse.__iter__()`` method. (Issue #1483) + +1.24.3 (2019-05-01) +------------------- + +* Apply fix for CVE-2019-9740. (Pull #1591) + +1.24.2 (2019-04-17) +------------------- + +* Don't load system certificates by default when any other ``ca_certs``, ``ca_certs_dir`` or + ``ssl_context`` parameters are specified. + +* Remove Authorization header regardless of case when redirecting to cross-site. (Issue #1510) + +* Add support for IPv6 addresses in subjectAltName section of certificates. (Issue #1269) + + +1.24.1 (2018-11-02) +------------------- + +* Remove quadratic behavior within ``GzipDecoder.decompress()`` (Issue #1467) + +* Restored functionality of ``ciphers`` parameter for ``create_urllib3_context()``. (Issue #1462) + + +1.24 (2018-10-16) +----------------- + +* Allow key_server_hostname to be specified when initializing a PoolManager to allow custom SNI to be overridden. (Pull #1449) + +* Test against Python 3.7 on AppVeyor. (Pull #1453) + +* Early-out ipv6 checks when running on App Engine. (Pull #1450) + +* Change ambiguous description of backoff_factor (Pull #1436) + +* Add ability to handle multiple Content-Encodings (Issue #1441 and Pull #1442) + +* Skip DNS names that can't be idna-decoded when using pyOpenSSL (Issue #1405). + +* Add a server_hostname parameter to HTTPSConnection which allows for + overriding the SNI hostname sent in the handshake. (Pull #1397) + +* Drop support for EOL Python 2.6 (Pull #1429 and Pull #1430) + +* Fixed bug where responses with header Content-Type: message/* erroneously + raised HeaderParsingError, resulting in a warning being logged. (Pull #1439) + +* Move urllib3 to src/urllib3 (Pull #1409) + + +1.23 (2018-06-04) +----------------- + +* Allow providing a list of headers to strip from requests when redirecting + to a different host. Defaults to the ``Authorization`` header. Different + headers can be set via ``Retry.remove_headers_on_redirect``. (Issue #1316) + +* Fix ``util.selectors._fileobj_to_fd`` to accept ``long`` (Issue #1247). + +* Dropped Python 3.3 support. (Pull #1242) + +* Put the connection back in the pool when calling stream() or read_chunked() on + a chunked HEAD response. (Issue #1234) + +* Fixed pyOpenSSL-specific ssl client authentication issue when clients + attempted to auth via certificate + chain (Issue #1060) + +* Add the port to the connectionpool connect print (Pull #1251) + +* Don't use the ``uuid`` module to create multipart data boundaries. (Pull #1380) + +* ``read_chunked()`` on a closed response returns no chunks. (Issue #1088) + +* Add Python 2.6 support to ``contrib.securetransport`` (Pull #1359) + +* Added support for auth info in url for SOCKS proxy (Pull #1363) + + +1.22 (2017-07-20) +----------------- + +* Fixed missing brackets in ``HTTP CONNECT`` when connecting to IPv6 address via + IPv6 proxy. (Issue #1222) + +* Made the connection pool retry on ``SSLError``. The original ``SSLError`` + is available on ``MaxRetryError.reason``. (Issue #1112) + +* Drain and release connection before recursing on retry/redirect. Fixes + deadlocks with a blocking connectionpool. (Issue #1167) + +* Fixed compatibility for cookiejar. (Issue #1229) + +* pyopenssl: Use vendored version of ``six``. (Issue #1231) + + +1.21.1 (2017-05-02) +------------------- + +* Fixed SecureTransport issue that would cause long delays in response body + delivery. (Pull #1154) + +* Fixed regression in 1.21 that threw exceptions when users passed the + ``socket_options`` flag to the ``PoolManager``. (Issue #1165) + +* Fixed regression in 1.21 that threw exceptions when users passed the + ``assert_hostname`` or ``assert_fingerprint`` flag to the ``PoolManager``. + (Pull #1157) + + +1.21 (2017-04-25) +----------------- + +* Improved performance of certain selector system calls on Python 3.5 and + later. (Pull #1095) + +* Resolved issue where the PyOpenSSL backend would not wrap SysCallError + exceptions appropriately when sending data. (Pull #1125) + +* Selectors now detects a monkey-patched select module after import for modules + that patch the select module like eventlet, greenlet. (Pull #1128) + +* Reduced memory consumption when streaming zlib-compressed responses + (as opposed to raw deflate streams). (Pull #1129) + +* Connection pools now use the entire request context when constructing the + pool key. (Pull #1016) + +* ``PoolManager.connection_from_*`` methods now accept a new keyword argument, + ``pool_kwargs``, which are merged with the existing ``connection_pool_kw``. + (Pull #1016) + +* Add retry counter for ``status_forcelist``. (Issue #1147) + +* Added ``contrib`` module for using SecureTransport on macOS: + ``urllib3.contrib.securetransport``. (Pull #1122) + +* urllib3 now only normalizes the case of ``http://`` and ``https://`` schemes: + for schemes it does not recognise, it assumes they are case-sensitive and + leaves them unchanged. + (Issue #1080) + + +1.20 (2017-01-19) +----------------- + +* Added support for waiting for I/O using selectors other than select, + improving urllib3's behaviour with large numbers of concurrent connections. + (Pull #1001) + +* Updated the date for the system clock check. (Issue #1005) + +* ConnectionPools now correctly consider hostnames to be case-insensitive. + (Issue #1032) + +* Outdated versions of PyOpenSSL now cause the PyOpenSSL contrib module + to fail when it is injected, rather than at first use. (Pull #1063) + +* Outdated versions of cryptography now cause the PyOpenSSL contrib module + to fail when it is injected, rather than at first use. (Issue #1044) + +* Automatically attempt to rewind a file-like body object when a request is + retried or redirected. (Pull #1039) + +* Fix some bugs that occur when modules incautiously patch the queue module. + (Pull #1061) + +* Prevent retries from occurring on read timeouts for which the request method + was not in the method whitelist. (Issue #1059) + +* Changed the PyOpenSSL contrib module to lazily load idna to avoid + unnecessarily bloating the memory of programs that don't need it. (Pull + #1076) + +* Add support for IPv6 literals with zone identifiers. (Pull #1013) + +* Added support for socks5h:// and socks4a:// schemes when working with SOCKS + proxies, and controlled remote DNS appropriately. (Issue #1035) + + +1.19.1 (2016-11-16) +------------------- + +* Fixed AppEngine import that didn't function on Python 3.5. (Pull #1025) + + +1.19 (2016-11-03) +----------------- + +* urllib3 now respects Retry-After headers on 413, 429, and 503 responses when + using the default retry logic. (Pull #955) + +* Remove markers from setup.py to assist ancient setuptools versions. (Issue + #986) + +* Disallow superscripts and other integerish things in URL ports. (Issue #989) + +* Allow urllib3's HTTPResponse.stream() method to continue to work with + non-httplib underlying FPs. (Pull #990) + +* Empty filenames in multipart headers are now emitted as such, rather than + being suppressed. (Issue #1015) + +* Prefer user-supplied Host headers on chunked uploads. (Issue #1009) + + +1.18.1 (2016-10-27) +------------------- + +* CVE-2016-9015. Users who are using urllib3 version 1.17 or 1.18 along with + PyOpenSSL injection and OpenSSL 1.1.0 *must* upgrade to this version. This + release fixes a vulnerability whereby urllib3 in the above configuration + would silently fail to validate TLS certificates due to erroneously setting + invalid flags in OpenSSL's ``SSL_CTX_set_verify`` function. These erroneous + flags do not cause a problem in OpenSSL versions before 1.1.0, which + interprets the presence of any flag as requesting certificate validation. + + There is no PR for this patch, as it was prepared for simultaneous disclosure + and release. The master branch received the same fix in Pull #1010. + + +1.18 (2016-09-26) +----------------- + +* Fixed incorrect message for IncompleteRead exception. (Pull #973) + +* Accept ``iPAddress`` subject alternative name fields in TLS certificates. + (Issue #258) + +* Fixed consistency of ``HTTPResponse.closed`` between Python 2 and 3. + (Issue #977) + +* Fixed handling of wildcard certificates when using PyOpenSSL. (Issue #979) + + +1.17 (2016-09-06) +----------------- + +* Accept ``SSLContext`` objects for use in SSL/TLS negotiation. (Issue #835) + +* ConnectionPool debug log now includes scheme, host, and port. (Issue #897) + +* Substantially refactored documentation. (Issue #887) + +* Used URLFetch default timeout on AppEngine, rather than hardcoding our own. + (Issue #858) + +* Normalize the scheme and host in the URL parser (Issue #833) + +* ``HTTPResponse`` contains the last ``Retry`` object, which now also + contains retries history. (Issue #848) + +* Timeout can no longer be set as boolean, and must be greater than zero. + (Pull #924) + +* Removed pyasn1 and ndg-httpsclient from dependencies used for PyOpenSSL. We + now use cryptography and idna, both of which are already dependencies of + PyOpenSSL. (Pull #930) + +* Fixed infinite loop in ``stream`` when amt=None. (Issue #928) + +* Try to use the operating system's certificates when we are using an + ``SSLContext``. (Pull #941) + +* Updated cipher suite list to allow ChaCha20+Poly1305. AES-GCM is preferred to + ChaCha20, but ChaCha20 is then preferred to everything else. (Pull #947) + +* Updated cipher suite list to remove 3DES-based cipher suites. (Pull #958) + +* Removed the cipher suite fallback to allow HIGH ciphers. (Pull #958) + +* Implemented ``length_remaining`` to determine remaining content + to be read. (Pull #949) + +* Implemented ``enforce_content_length`` to enable exceptions when + incomplete data chunks are received. (Pull #949) + +* Dropped connection start, dropped connection reset, redirect, forced retry, + and new HTTPS connection log levels to DEBUG, from INFO. (Pull #967) + + +1.16 (2016-06-11) +----------------- + +* Disable IPv6 DNS when IPv6 connections are not possible. (Issue #840) + +* Provide ``key_fn_by_scheme`` pool keying mechanism that can be + overridden. (Issue #830) + +* Normalize scheme and host to lowercase for pool keys, and include + ``source_address``. (Issue #830) + +* Cleaner exception chain in Python 3 for ``_make_request``. + (Issue #861) + +* Fixed installing ``urllib3[socks]`` extra. (Issue #864) + +* Fixed signature of ``ConnectionPool.close`` so it can actually safely be + called by subclasses. (Issue #873) + +* Retain ``release_conn`` state across retries. (Issues #651, #866) + +* Add customizable ``HTTPConnectionPool.ResponseCls``, which defaults to + ``HTTPResponse`` but can be replaced with a subclass. (Issue #879) + + +1.15.1 (2016-04-11) +------------------- + +* Fix packaging to include backports module. (Issue #841) + + +1.15 (2016-04-06) +----------------- + +* Added Retry(raise_on_status=False). (Issue #720) + +* Always use setuptools, no more distutils fallback. (Issue #785) + +* Dropped support for Python 3.2. (Issue #786) + +* Chunked transfer encoding when requesting with ``chunked=True``. + (Issue #790) + +* Fixed regression with IPv6 port parsing. (Issue #801) + +* Append SNIMissingWarning messages to allow users to specify it in + the PYTHONWARNINGS environment variable. (Issue #816) + +* Handle unicode headers in Py2. (Issue #818) + +* Log certificate when there is a hostname mismatch. (Issue #820) + +* Preserve order of request/response headers. (Issue #821) + + +1.14 (2015-12-29) +----------------- + +* contrib: SOCKS proxy support! (Issue #762) + +* Fixed AppEngine handling of transfer-encoding header and bug + in Timeout defaults checking. (Issue #763) + + +1.13.1 (2015-12-18) +------------------- + +* Fixed regression in IPv6 + SSL for match_hostname. (Issue #761) + + +1.13 (2015-12-14) +----------------- + +* Fixed ``pip install urllib3[secure]`` on modern pip. (Issue #706) + +* pyopenssl: Fixed SSL3_WRITE_PENDING error. (Issue #717) + +* pyopenssl: Support for TLSv1.1 and TLSv1.2. (Issue #696) + +* Close connections more defensively on exception. (Issue #734) + +* Adjusted ``read_chunked`` to handle gzipped, chunk-encoded bodies without + repeatedly flushing the decoder, to function better on Jython. (Issue #743) + +* Accept ``ca_cert_dir`` for SSL-related PoolManager configuration. (Issue #758) + + +1.12 (2015-09-03) +----------------- + +* Rely on ``six`` for importing ``httplib`` to work around + conflicts with other Python 3 shims. (Issue #688) + +* Add support for directories of certificate authorities, as supported by + OpenSSL. (Issue #701) + +* New exception: ``NewConnectionError``, raised when we fail to establish + a new connection, usually ``ECONNREFUSED`` socket error. + + +1.11 (2015-07-21) +----------------- + +* When ``ca_certs`` is given, ``cert_reqs`` defaults to + ``'CERT_REQUIRED'``. (Issue #650) + +* ``pip install urllib3[secure]`` will install Certifi and + PyOpenSSL as dependencies. (Issue #678) + +* Made ``HTTPHeaderDict`` usable as a ``headers`` input value + (Issues #632, #679) + +* Added `urllib3.contrib.appengine `_ + which has an ``AppEngineManager`` for using ``URLFetch`` in a + Google AppEngine environment. (Issue #664) + +* Dev: Added test suite for AppEngine. (Issue #631) + +* Fix performance regression when using PyOpenSSL. (Issue #626) + +* Passing incorrect scheme (e.g. ``foo://``) will raise + ``ValueError`` instead of ``AssertionError`` (backwards + compatible for now, but please migrate). (Issue #640) + +* Fix pools not getting replenished when an error occurs during a + request using ``release_conn=False``. (Issue #644) + +* Fix pool-default headers not applying for url-encoded requests + like GET. (Issue #657) + +* log.warning in Python 3 when headers are skipped due to parsing + errors. (Issue #642) + +* Close and discard connections if an error occurs during read. + (Issue #660) + +* Fix host parsing for IPv6 proxies. (Issue #668) + +* Separate warning type SubjectAltNameWarning, now issued once + per host. (Issue #671) + +* Fix ``httplib.IncompleteRead`` not getting converted to + ``ProtocolError`` when using ``HTTPResponse.stream()`` + (Issue #674) + +1.10.4 (2015-05-03) +------------------- + +* Migrate tests to Tornado 4. (Issue #594) + +* Append default warning configuration rather than overwrite. + (Issue #603) + +* Fix streaming decoding regression. (Issue #595) + +* Fix chunked requests losing state across keep-alive connections. + (Issue #599) + +* Fix hanging when chunked HEAD response has no body. (Issue #605) + + +1.10.3 (2015-04-21) +------------------- + +* Emit ``InsecurePlatformWarning`` when SSLContext object is missing. + (Issue #558) + +* Fix regression of duplicate header keys being discarded. + (Issue #563) + +* ``Response.stream()`` returns a generator for chunked responses. + (Issue #560) + +* Set upper-bound timeout when waiting for a socket in PyOpenSSL. + (Issue #585) + +* Work on platforms without `ssl` module for plain HTTP requests. + (Issue #587) + +* Stop relying on the stdlib's default cipher list. (Issue #588) + + +1.10.2 (2015-02-25) +------------------- + +* Fix file descriptor leakage on retries. (Issue #548) + +* Removed RC4 from default cipher list. (Issue #551) + +* Header performance improvements. (Issue #544) + +* Fix PoolManager not obeying redirect retry settings. (Issue #553) + + +1.10.1 (2015-02-10) +------------------- + +* Pools can be used as context managers. (Issue #545) + +* Don't re-use connections which experienced an SSLError. (Issue #529) + +* Don't fail when gzip decoding an empty stream. (Issue #535) + +* Add sha256 support for fingerprint verification. (Issue #540) + +* Fixed handling of header values containing commas. (Issue #533) + + +1.10 (2014-12-14) +----------------- + +* Disabled SSLv3. (Issue #473) + +* Add ``Url.url`` property to return the composed url string. (Issue #394) + +* Fixed PyOpenSSL + gevent ``WantWriteError``. (Issue #412) + +* ``MaxRetryError.reason`` will always be an exception, not string. + (Issue #481) + +* Fixed SSL-related timeouts not being detected as timeouts. (Issue #492) + +* Py3: Use ``ssl.create_default_context()`` when available. (Issue #473) + +* Emit ``InsecureRequestWarning`` for *every* insecure HTTPS request. + (Issue #496) + +* Emit ``SecurityWarning`` when certificate has no ``subjectAltName``. + (Issue #499) + +* Close and discard sockets which experienced SSL-related errors. + (Issue #501) + +* Handle ``body`` param in ``.request(...)``. (Issue #513) + +* Respect timeout with HTTPS proxy. (Issue #505) + +* PyOpenSSL: Handle ZeroReturnError exception. (Issue #520) + + +1.9.1 (2014-09-13) +------------------ + +* Apply socket arguments before binding. (Issue #427) + +* More careful checks if fp-like object is closed. (Issue #435) + +* Fixed packaging issues of some development-related files not + getting included. (Issue #440) + +* Allow performing *only* fingerprint verification. (Issue #444) + +* Emit ``SecurityWarning`` if system clock is waaay off. (Issue #445) + +* Fixed PyOpenSSL compatibility with PyPy. (Issue #450) + +* Fixed ``BrokenPipeError`` and ``ConnectionError`` handling in Py3. + (Issue #443) + + + +1.9 (2014-07-04) +---------------- + +* Shuffled around development-related files. If you're maintaining a distro + package of urllib3, you may need to tweak things. (Issue #415) + +* Unverified HTTPS requests will trigger a warning on the first request. See + our new `security documentation + `_ for details. + (Issue #426) + +* New retry logic and ``urllib3.util.retry.Retry`` configuration object. + (Issue #326) + +* All raised exceptions should now wrapped in a + ``urllib3.exceptions.HTTPException``-extending exception. (Issue #326) + +* All errors during a retry-enabled request should be wrapped in + ``urllib3.exceptions.MaxRetryError``, including timeout-related exceptions + which were previously exempt. Underlying error is accessible from the + ``.reason`` property. (Issue #326) + +* ``urllib3.exceptions.ConnectionError`` renamed to + ``urllib3.exceptions.ProtocolError``. (Issue #326) + +* Errors during response read (such as IncompleteRead) are now wrapped in + ``urllib3.exceptions.ProtocolError``. (Issue #418) + +* Requesting an empty host will raise ``urllib3.exceptions.LocationValueError``. + (Issue #417) + +* Catch read timeouts over SSL connections as + ``urllib3.exceptions.ReadTimeoutError``. (Issue #419) + +* Apply socket arguments before connecting. (Issue #427) + + +1.8.3 (2014-06-23) +------------------ + +* Fix TLS verification when using a proxy in Python 3.4.1. (Issue #385) + +* Add ``disable_cache`` option to ``urllib3.util.make_headers``. (Issue #393) + +* Wrap ``socket.timeout`` exception with + ``urllib3.exceptions.ReadTimeoutError``. (Issue #399) + +* Fixed proxy-related bug where connections were being reused incorrectly. + (Issues #366, #369) + +* Added ``socket_options`` keyword parameter which allows to define + ``setsockopt`` configuration of new sockets. (Issue #397) + +* Removed ``HTTPConnection.tcp_nodelay`` in favor of + ``HTTPConnection.default_socket_options``. (Issue #397) + +* Fixed ``TypeError`` bug in Python 2.6.4. (Issue #411) + + +1.8.2 (2014-04-17) +------------------ + +* Fix ``urllib3.util`` not being included in the package. + + +1.8.1 (2014-04-17) +------------------ + +* Fix AppEngine bug of HTTPS requests going out as HTTP. (Issue #356) + +* Don't install ``dummyserver`` into ``site-packages`` as it's only needed + for the test suite. (Issue #362) + +* Added support for specifying ``source_address``. (Issue #352) + + +1.8 (2014-03-04) +---------------- + +* Improved url parsing in ``urllib3.util.parse_url`` (properly parse '@' in + username, and blank ports like 'hostname:'). + +* New ``urllib3.connection`` module which contains all the HTTPConnection + objects. + +* Several ``urllib3.util.Timeout``-related fixes. Also changed constructor + signature to a more sensible order. [Backwards incompatible] + (Issues #252, #262, #263) + +* Use ``backports.ssl_match_hostname`` if it's installed. (Issue #274) + +* Added ``.tell()`` method to ``urllib3.response.HTTPResponse`` which + returns the number of bytes read so far. (Issue #277) + +* Support for platforms without threading. (Issue #289) + +* Expand default-port comparison in ``HTTPConnectionPool.is_same_host`` + to allow a pool with no specified port to be considered equal to to an + HTTP/HTTPS url with port 80/443 explicitly provided. (Issue #305) + +* Improved default SSL/TLS settings to avoid vulnerabilities. + (Issue #309) + +* Fixed ``urllib3.poolmanager.ProxyManager`` not retrying on connect errors. + (Issue #310) + +* Disable Nagle's Algorithm on the socket for non-proxies. A subset of requests + will send the entire HTTP request ~200 milliseconds faster; however, some of + the resulting TCP packets will be smaller. (Issue #254) + +* Increased maximum number of SubjectAltNames in ``urllib3.contrib.pyopenssl`` + from the default 64 to 1024 in a single certificate. (Issue #318) + +* Headers are now passed and stored as a custom + ``urllib3.collections_.HTTPHeaderDict`` object rather than a plain ``dict``. + (Issue #329, #333) + +* Headers no longer lose their case on Python 3. (Issue #236) + +* ``urllib3.contrib.pyopenssl`` now uses the operating system's default CA + certificates on inject. (Issue #332) + +* Requests with ``retries=False`` will immediately raise any exceptions without + wrapping them in ``MaxRetryError``. (Issue #348) + +* Fixed open socket leak with SSL-related failures. (Issue #344, #348) + + +1.7.1 (2013-09-25) +------------------ + +* Added granular timeout support with new ``urllib3.util.Timeout`` class. + (Issue #231) + +* Fixed Python 3.4 support. (Issue #238) + + +1.7 (2013-08-14) +---------------- + +* More exceptions are now pickle-able, with tests. (Issue #174) + +* Fixed redirecting with relative URLs in Location header. (Issue #178) + +* Support for relative urls in ``Location: ...`` header. (Issue #179) + +* ``urllib3.response.HTTPResponse`` now inherits from ``io.IOBase`` for bonus + file-like functionality. (Issue #187) + +* Passing ``assert_hostname=False`` when creating a HTTPSConnectionPool will + skip hostname verification for SSL connections. (Issue #194) + +* New method ``urllib3.response.HTTPResponse.stream(...)`` which acts as a + generator wrapped around ``.read(...)``. (Issue #198) + +* IPv6 url parsing enforces brackets around the hostname. (Issue #199) + +* Fixed thread race condition in + ``urllib3.poolmanager.PoolManager.connection_from_host(...)`` (Issue #204) + +* ``ProxyManager`` requests now include non-default port in ``Host: ...`` + header. (Issue #217) + +* Added HTTPS proxy support in ``ProxyManager``. (Issue #170 #139) + +* New ``RequestField`` object can be passed to the ``fields=...`` param which + can specify headers. (Issue #220) + +* Raise ``urllib3.exceptions.ProxyError`` when connecting to proxy fails. + (Issue #221) + +* Use international headers when posting file names. (Issue #119) + +* Improved IPv6 support. (Issue #203) + + +1.6 (2013-04-25) +---------------- + +* Contrib: Optional SNI support for Py2 using PyOpenSSL. (Issue #156) + +* ``ProxyManager`` automatically adds ``Host: ...`` header if not given. + +* Improved SSL-related code. ``cert_req`` now optionally takes a string like + "REQUIRED" or "NONE". Same with ``ssl_version`` takes strings like "SSLv23" + The string values reflect the suffix of the respective constant variable. + (Issue #130) + +* Vendored ``socksipy`` now based on Anorov's fork which handles unexpectedly + closed proxy connections and larger read buffers. (Issue #135) + +* Ensure the connection is closed if no data is received, fixes connection leak + on some platforms. (Issue #133) + +* Added SNI support for SSL/TLS connections on Py32+. (Issue #89) + +* Tests fixed to be compatible with Py26 again. (Issue #125) + +* Added ability to choose SSL version by passing an ``ssl.PROTOCOL_*`` constant + to the ``ssl_version`` parameter of ``HTTPSConnectionPool``. (Issue #109) + +* Allow an explicit content type to be specified when encoding file fields. + (Issue #126) + +* Exceptions are now pickleable, with tests. (Issue #101) + +* Fixed default headers not getting passed in some cases. (Issue #99) + +* Treat "content-encoding" header value as case-insensitive, per RFC 2616 + Section 3.5. (Issue #110) + +* "Connection Refused" SocketErrors will get retried rather than raised. + (Issue #92) + +* Updated vendored ``six``, no longer overrides the global ``six`` module + namespace. (Issue #113) + +* ``urllib3.exceptions.MaxRetryError`` contains a ``reason`` property holding + the exception that prompted the final retry. If ``reason is None`` then it + was due to a redirect. (Issue #92, #114) + +* Fixed ``PoolManager.urlopen()`` from not redirecting more than once. + (Issue #149) + +* Don't assume ``Content-Type: text/plain`` for multi-part encoding parameters + that are not files. (Issue #111) + +* Pass `strict` param down to ``httplib.HTTPConnection``. (Issue #122) + +* Added mechanism to verify SSL certificates by fingerprint (md5, sha1) or + against an arbitrary hostname (when connecting by IP or for misconfigured + servers). (Issue #140) + +* Streaming decompression support. (Issue #159) + + +1.5 (2012-08-02) +---------------- + +* Added ``urllib3.add_stderr_logger()`` for quickly enabling STDERR debug + logging in urllib3. + +* Native full URL parsing (including auth, path, query, fragment) available in + ``urllib3.util.parse_url(url)``. + +* Built-in redirect will switch method to 'GET' if status code is 303. + (Issue #11) + +* ``urllib3.PoolManager`` strips the scheme and host before sending the request + uri. (Issue #8) + +* New ``urllib3.exceptions.DecodeError`` exception for when automatic decoding, + based on the Content-Type header, fails. + +* Fixed bug with pool depletion and leaking connections (Issue #76). Added + explicit connection closing on pool eviction. Added + ``urllib3.PoolManager.clear()``. + +* 99% -> 100% unit test coverage. + + +1.4 (2012-06-16) +---------------- + +* Minor AppEngine-related fixes. + +* Switched from ``mimetools.choose_boundary`` to ``uuid.uuid4()``. + +* Improved url parsing. (Issue #73) + +* IPv6 url support. (Issue #72) + + +1.3 (2012-03-25) +---------------- + +* Removed pre-1.0 deprecated API. + +* Refactored helpers into a ``urllib3.util`` submodule. + +* Fixed multipart encoding to support list-of-tuples for keys with multiple + values. (Issue #48) + +* Fixed multiple Set-Cookie headers in response not getting merged properly in + Python 3. (Issue #53) + +* AppEngine support with Py27. (Issue #61) + +* Minor ``encode_multipart_formdata`` fixes related to Python 3 strings vs + bytes. + + +1.2.2 (2012-02-06) +------------------ + +* Fixed packaging bug of not shipping ``test-requirements.txt``. (Issue #47) + + +1.2.1 (2012-02-05) +------------------ + +* Fixed another bug related to when ``ssl`` module is not available. (Issue #41) + +* Location parsing errors now raise ``urllib3.exceptions.LocationParseError`` + which inherits from ``ValueError``. + + +1.2 (2012-01-29) +---------------- + +* Added Python 3 support (tested on 3.2.2) + +* Dropped Python 2.5 support (tested on 2.6.7, 2.7.2) + +* Use ``select.poll`` instead of ``select.select`` for platforms that support + it. + +* Use ``Queue.LifoQueue`` instead of ``Queue.Queue`` for more aggressive + connection reusing. Configurable by overriding ``ConnectionPool.QueueCls``. + +* Fixed ``ImportError`` during install when ``ssl`` module is not available. + (Issue #41) + +* Fixed ``PoolManager`` redirects between schemes (such as HTTP -> HTTPS) not + completing properly. (Issue #28, uncovered by Issue #10 in v1.1) + +* Ported ``dummyserver`` to use ``tornado`` instead of ``webob`` + + ``eventlet``. Removed extraneous unsupported dummyserver testing backends. + Added socket-level tests. + +* More tests. Achievement Unlocked: 99% Coverage. + + +1.1 (2012-01-07) +---------------- + +* Refactored ``dummyserver`` to its own root namespace module (used for + testing). + +* Added hostname verification for ``VerifiedHTTPSConnection`` by vendoring in + Py32's ``ssl_match_hostname``. (Issue #25) + +* Fixed cross-host HTTP redirects when using ``PoolManager``. (Issue #10) + +* Fixed ``decode_content`` being ignored when set through ``urlopen``. (Issue + #27) + +* Fixed timeout-related bugs. (Issues #17, #23) + + +1.0.2 (2011-11-04) +------------------ + +* Fixed typo in ``VerifiedHTTPSConnection`` which would only present as a bug if + you're using the object manually. (Thanks pyos) + +* Made RecentlyUsedContainer (and consequently PoolManager) more thread-safe by + wrapping the access log in a mutex. (Thanks @christer) + +* Made RecentlyUsedContainer more dict-like (corrected ``__delitem__`` and + ``__getitem__`` behaviour), with tests. Shouldn't affect core urllib3 code. + + +1.0.1 (2011-10-10) +------------------ + +* Fixed a bug where the same connection would get returned into the pool twice, + causing extraneous "HttpConnectionPool is full" log warnings. + + +1.0 (2011-10-08) +---------------- + +* Added ``PoolManager`` with LRU expiration of connections (tested and + documented). +* Added ``ProxyManager`` (needs tests, docs, and confirmation that it works + with HTTPS proxies). +* Added optional partial-read support for responses when + ``preload_content=False``. You can now make requests and just read the headers + without loading the content. +* Made response decoding optional (default on, same as before). +* Added optional explicit boundary string for ``encode_multipart_formdata``. +* Convenience request methods are now inherited from ``RequestMethods``. Old + helpers like ``get_url`` and ``post_url`` should be abandoned in favour of + the new ``request(method, url, ...)``. +* Refactored code to be even more decoupled, reusable, and extendable. +* License header added to ``.py`` files. +* Embiggened the documentation: Lots of Sphinx-friendly docstrings in the code + and docs in ``docs/`` and on https://urllib3.readthedocs.io/. +* Embettered all the things! +* Started writing this file. + + +0.4.1 (2011-07-17) +------------------ + +* Minor bug fixes, code cleanup. + + +0.4 (2011-03-01) +---------------- + +* Better unicode support. +* Added ``VerifiedHTTPSConnection``. +* Added ``NTLMConnectionPool`` in contrib. +* Minor improvements. + + +0.3.1 (2010-07-13) +------------------ + +* Added ``assert_host_name`` optional parameter. Now compatible with proxies. + + +0.3 (2009-12-10) +---------------- + +* Added HTTPS support. +* Minor bug fixes. +* Refactored, broken backwards compatibility with 0.2. +* API to be treated as stable from this version forward. + + +0.2 (2008-11-17) +---------------- + +* Added unit tests. +* Bug fixes. + + +0.1 (2008-11-16) +---------------- + +* First release. diff --git a/lib/urllib3-1.26.15.dist-info/RECORD b/lib/urllib3-1.26.15.dist-info/RECORD new file mode 100644 index 0000000..bcd7eef --- /dev/null +++ b/lib/urllib3-1.26.15.dist-info/RECORD @@ -0,0 +1,82 @@ +urllib3-1.26.15.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +urllib3-1.26.15.dist-info/LICENSE.txt,sha256=w3vxhuJ8-dvpYZ5V7f486nswCRzrPaY8fay-Dm13kHs,1115 +urllib3-1.26.15.dist-info/METADATA,sha256=blT5BzPICGt1qpMvC6GdfIBUNNMobmh1bXPyuOQmq30,48125 +urllib3-1.26.15.dist-info/RECORD,, +urllib3-1.26.15.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110 +urllib3-1.26.15.dist-info/top_level.txt,sha256=EMiXL2sKrTcmrMxIHTqdc3ET54pQI2Y072LexFEemvo,8 +urllib3/__init__.py,sha256=iXLcYiJySn0GNbWOOZDDApgBL1JgP44EZ8i1760S8Mc,3333 +urllib3/__pycache__/__init__.cpython-39.pyc,, +urllib3/__pycache__/_collections.cpython-39.pyc,, +urllib3/__pycache__/_version.cpython-39.pyc,, +urllib3/__pycache__/connection.cpython-39.pyc,, +urllib3/__pycache__/connectionpool.cpython-39.pyc,, +urllib3/__pycache__/exceptions.cpython-39.pyc,, +urllib3/__pycache__/fields.cpython-39.pyc,, +urllib3/__pycache__/filepost.cpython-39.pyc,, +urllib3/__pycache__/poolmanager.cpython-39.pyc,, +urllib3/__pycache__/request.cpython-39.pyc,, +urllib3/__pycache__/response.cpython-39.pyc,, +urllib3/_collections.py,sha256=Rp1mVyBgc_UlAcp6M3at1skJBXR5J43NawRTvW2g_XY,10811 +urllib3/_version.py,sha256=vFwhFPO1DTzD8xawsdSDwriGSheS7LurJQL9fSgM_IM,64 +urllib3/connection.py,sha256=92k9td_y4PEiTIjNufCUa1NzMB3J3w0LEdyokYgXnW8,20300 +urllib3/connectionpool.py,sha256=u7I7TzJTsicVoNjGeZkCD5LANp_GCeDNBwXZoGHHVLo,39128 +urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/contrib/__pycache__/__init__.cpython-39.pyc,, +urllib3/contrib/__pycache__/_appengine_environ.cpython-39.pyc,, +urllib3/contrib/__pycache__/appengine.cpython-39.pyc,, +urllib3/contrib/__pycache__/ntlmpool.cpython-39.pyc,, +urllib3/contrib/__pycache__/pyopenssl.cpython-39.pyc,, +urllib3/contrib/__pycache__/securetransport.cpython-39.pyc,, +urllib3/contrib/__pycache__/socks.cpython-39.pyc,, +urllib3/contrib/_appengine_environ.py,sha256=bDbyOEhW2CKLJcQqAKAyrEHN-aklsyHFKq6vF8ZFsmk,957 +urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/contrib/_securetransport/__pycache__/__init__.cpython-39.pyc,, +urllib3/contrib/_securetransport/__pycache__/bindings.cpython-39.pyc,, +urllib3/contrib/_securetransport/__pycache__/low_level.cpython-39.pyc,, +urllib3/contrib/_securetransport/bindings.py,sha256=4Xk64qIkPBt09A5q-RIFUuDhNc9mXilVapm7WnYnzRw,17632 +urllib3/contrib/_securetransport/low_level.py,sha256=B2JBB2_NRP02xK6DCa1Pa9IuxrPwxzDzZbixQkb7U9M,13922 +urllib3/contrib/appengine.py,sha256=6IBW6lPOoVUxASPwtn6IH1AATe5DK3lLJCfwyWlLKAE,11012 +urllib3/contrib/ntlmpool.py,sha256=NlfkW7WMdW8ziqudopjHoW299og1BTWi0IeIibquFwk,4528 +urllib3/contrib/pyopenssl.py,sha256=4AJAlo9NmjWofY4dJwRa4kbZuRuHfNJxu8Pv6yQk1ss,17055 +urllib3/contrib/securetransport.py,sha256=QOhVbWrFQTKbmV-vtyG69amekkKVxXkdjk9oymaO0Ag,34416 +urllib3/contrib/socks.py,sha256=aRi9eWXo9ZEb95XUxef4Z21CFlnnjbEiAo9HOseoMt4,7097 +urllib3/exceptions.py,sha256=0Mnno3KHTNfXRfY7638NufOPkUb6mXOm-Lqj-4x2w8A,8217 +urllib3/fields.py,sha256=kvLDCg_JmH1lLjUUEY_FLS8UhY7hBvDPuVETbY8mdrM,8579 +urllib3/filepost.py,sha256=5b_qqgRHVlL7uLtdAYBzBh-GHmU5AfJVt_2N0XS3PeY,2440 +urllib3/packages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/packages/__pycache__/__init__.cpython-39.pyc,, +urllib3/packages/__pycache__/six.cpython-39.pyc,, +urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/packages/backports/__pycache__/__init__.cpython-39.pyc,, +urllib3/packages/backports/__pycache__/makefile.cpython-39.pyc,, +urllib3/packages/backports/makefile.py,sha256=nbzt3i0agPVP07jqqgjhaYjMmuAi_W5E0EywZivVO8E,1417 +urllib3/packages/six.py,sha256=b9LM0wBXv7E7SrbCjAm4wwN-hrH-iNxv18LgWNMMKPo,34665 +urllib3/poolmanager.py,sha256=0KOOJECoeLYVjUHvv-0h4Oq3FFQQ2yb-Fnjkbj8gJO0,19786 +urllib3/request.py,sha256=ZFSIqX0C6WizixecChZ3_okyu7BEv0lZu1VT0s6h4SM,5985 +urllib3/response.py,sha256=UPgLmnHj4z71ZnH8ivYOyncATifTOw9FQukUqDnckCc,30761 +urllib3/util/__init__.py,sha256=JEmSmmqqLyaw8P51gUImZh8Gwg9i1zSe-DoqAitn2nc,1155 +urllib3/util/__pycache__/__init__.cpython-39.pyc,, +urllib3/util/__pycache__/connection.cpython-39.pyc,, +urllib3/util/__pycache__/proxy.cpython-39.pyc,, +urllib3/util/__pycache__/queue.cpython-39.pyc,, +urllib3/util/__pycache__/request.cpython-39.pyc,, +urllib3/util/__pycache__/response.cpython-39.pyc,, +urllib3/util/__pycache__/retry.cpython-39.pyc,, +urllib3/util/__pycache__/ssl_.cpython-39.pyc,, +urllib3/util/__pycache__/ssl_match_hostname.cpython-39.pyc,, +urllib3/util/__pycache__/ssltransport.cpython-39.pyc,, +urllib3/util/__pycache__/timeout.cpython-39.pyc,, +urllib3/util/__pycache__/url.cpython-39.pyc,, +urllib3/util/__pycache__/wait.cpython-39.pyc,, +urllib3/util/connection.py,sha256=5Lx2B1PW29KxBn2T0xkN1CBgRBa3gGVJBKoQoRogEVk,4901 +urllib3/util/proxy.py,sha256=zUvPPCJrp6dOF0N4GAVbOcl6o-4uXKSrGiTkkr5vUS4,1605 +urllib3/util/queue.py,sha256=nRgX8_eX-_VkvxoX096QWoz8Ps0QHUAExILCY_7PncM,498 +urllib3/util/request.py,sha256=fWiAaa8pwdLLIqoTLBxCC2e4ed80muzKU3e3HWWTzFQ,4225 +urllib3/util/response.py,sha256=GJpg3Egi9qaJXRwBh5wv-MNuRWan5BIu40oReoxWP28,3510 +urllib3/util/retry.py,sha256=4laWh0HpwGijLiBmdBIYtbhYekQnNzzhx2W9uys0RHA,22003 +urllib3/util/ssl_.py,sha256=c0sYiSC6272r6uPkxQpo5rYPP9QC1eR6oI7004gYqZo,17165 +urllib3/util/ssl_match_hostname.py,sha256=Ir4cZVEjmAk8gUAIHWSi7wtOO83UCYABY2xFD1Ql_WA,5758 +urllib3/util/ssltransport.py,sha256=NA-u5rMTrDFDFC8QzRKUEKMG0561hOD4qBTr3Z4pv6E,6895 +urllib3/util/timeout.py,sha256=cwq4dMk87mJHSBktK1miYJ-85G-3T3RmT20v7SFCpno,10168 +urllib3/util/url.py,sha256=kMxL1k0d-aQm_iZDw_zMmnyYyjrIA_DbsMy3cm3V55M,14279 +urllib3/util/wait.py,sha256=fOX0_faozG2P7iVojQoE1mbydweNyTcm-hXEfFrTtLI,5403 diff --git a/lib/urllib3-1.26.15.dist-info/WHEEL b/lib/urllib3-1.26.15.dist-info/WHEEL new file mode 100644 index 0000000..9d8f872 --- /dev/null +++ b/lib/urllib3-1.26.15.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/lib/urllib3-1.26.15.dist-info/top_level.txt b/lib/urllib3-1.26.15.dist-info/top_level.txt new file mode 100644 index 0000000..a42590b --- /dev/null +++ b/lib/urllib3-1.26.15.dist-info/top_level.txt @@ -0,0 +1 @@ +urllib3 diff --git a/lib/urllib3/__init__.py b/lib/urllib3/__init__.py new file mode 100644 index 0000000..c6fa382 --- /dev/null +++ b/lib/urllib3/__init__.py @@ -0,0 +1,102 @@ +""" +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more +""" +from __future__ import absolute_import + +# Set default logging handler to avoid "No handler found" warnings. +import logging +import warnings +from logging import NullHandler + +from . import exceptions +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url +from .filepost import encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import HTTPResponse +from .util.request import make_headers +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host + +# === NOTE TO REPACKAGERS AND VENDORS === +# Please delete this block, this logic is only +# for urllib3 being distributed via PyPI. +# See: https://github.com/urllib3/urllib3/issues/2680 +try: + import urllib3_secure_extra # type: ignore # noqa: F401 +except ImportError: + pass +else: + warnings.warn( + "'urllib3[secure]' extra is deprecated and will be removed " + "in a future release of urllib3 2.x. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2680", + category=DeprecationWarning, + stacklevel=2, + ) + +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ + +__all__ = ( + "HTTPConnectionPool", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "get_host", + "make_headers", + "proxy_from_url", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger(level=logging.DEBUG): + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug("Added a stderr logging handler to logger: %s", __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) + + +def disable_warnings(category=exceptions.HTTPWarning): + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter("ignore", category) diff --git a/lib/urllib3/__pycache__/__init__.cpython-39.pyc b/lib/urllib3/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be19a003e2ef8485e0dd94ba8bdf01f52c02bb6b GIT binary patch literal 2484 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 X1R9(}hIl?-1oA72IY=OD&E5Zhm)yt% literal 0 HcmV?d00001 diff --git a/lib/urllib3/__pycache__/_version.cpython-39.pyc b/lib/urllib3/__pycache__/_version.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfae9b62c162379fc210f702186cee5dc25eb29c GIT binary patch literal 191 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 d{|`n-jJ*H= literal 0 HcmV?d00001 diff --git a/lib/urllib3/__pycache__/exceptions.cpython-39.pyc b/lib/urllib3/__pycache__/exceptions.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e56dab8d5165f9264a683559ecd3336a8e85d85 GIT binary patch literal 11623 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 LeGumlgFE_v_;@rX literal 0 HcmV?d00001 diff --git a/lib/urllib3/__pycache__/fields.cpython-39.pyc b/lib/urllib3/__pycache__/fields.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c1f13763314c621e69cb39ff8b57d9e3aba2ca7 GIT binary patch literal 8138 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 literal 0 HcmV?d00001 diff --git a/lib/urllib3/__pycache__/filepost.cpython-39.pyc b/lib/urllib3/__pycache__/filepost.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78e45e99b94519896dae6f17af85640f2cb4884d GIT binary patch literal 2739 zcmbVO&u<$=6rR~#Z#Ek{N!?HgDrHotz*6j%BB9EJf@nYl(G&MgKqhl1k*)faW>R8a5VI{IVHmtFDC7g+zj#J2X zI2*Ygw~%MTxv1KyQXXujYLR1&6I^nz%VNWB4bGoK!4q1GRq&~i=nTk$9rN`D@Rki&OV1^dv^ z!C@uU!{(h6e4~#Frv*1wpx%S3-iAh#BdU!flhb4Pjfu8)DvD*~$mRx)GXj?~4>w!} zD=)6&^Gd+%6PMic#qKA`-D?!$h(uh=fj(P__K+BvGO_NYa>l@}N~H zI$4CH^NOeZAZX0w&LD^jIiJ}U`(ZFV$c;qhGpY1qoPu)ez^CaG zLta6MGXj9p*wI!-o`96mHS+NK*nm}ruE450nehXmomJHduyDMz3!E_1+QVft}Jn*$zDMVbPU{$fT z?T28{cK@dd5Uq*g^X}E39J2xTas#lw(q8O~+(Mnx^o)jl|n46S2mY%eI2*Z}-Tym8 z{T23jJMgz55_KwL_40OQ6OjL-8biGG@b)ub_Md8)PQ=D$Y`gFTVJOxpVhz!gqxmNY$lFhP(li zZ{T=R@Eg{`dw&B#lP3hR<1{6537CY8fvk}|fH_8EGXqx97KasAVD1=lm#$>S6AD~o z9@)phH?WF6zw+86|Gw?4zrb)2wr+ zxx7-2#&GUY?U-bhA!ae#`(_$RpT$Vn%qz$Tyc===Mn}J2@<6{2ehsMvLO`%64RtW` zq`nC`Bx-U`d+h%D{m(F^M4p~_Xd6~|(S-ckg`+blYrsQi`b+H$QqdV5ii4Gxi^|4} zCrvZW*;(sD7AKmik?;dJ34#-@AHyTdgvF*i*yA7!*+4M#WaMpLzV`IelXyPk-AMwv zkbHoA@rfIUcC&dy)m(*cQ*#d;95$OE{S$T{p5Ei58>%rcP1saqt%5l7%3~yJIRmq~ zUCQ~q6ftDV-Zn4}q*7pautDMPxf#S7{$Xf2A$u+Mbx$9RMDA<}z2}9gXxL>CTtb!B zAmIn{DxTTEan(;^4gU{{Js0C-Srl&4JN1Abnj8=R81^a#4Vkm4Lv5o<>qZ@V%m7uo zXt+kzs6&;vU`>N=G%n|EuQyDA21TzY5h*zbO>Uzj`byStU<65tM-jC}qH?DH2Z{%F zs?%W${#8v-3X^0v9m#i4C#L@b$Rd!7)6c!?lss~0HA;9Iio2Mc)N9Z{27{M4kjbhP Ne#X4%xXzMO{|kGa!5RPn literal 0 HcmV?d00001 diff --git a/lib/urllib3/__pycache__/poolmanager.cpython-39.pyc b/lib/urllib3/__pycache__/poolmanager.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ce912242750c473787c33360b6312f5715e8d84 GIT binary patch literal 15158 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 literal 0 HcmV?d00001 diff --git a/lib/urllib3/_collections.py b/lib/urllib3/_collections.py new file mode 100644 index 0000000..da9857e --- /dev/null +++ b/lib/urllib3/_collections.py @@ -0,0 +1,337 @@ +from __future__ import absolute_import + +try: + from collections.abc import Mapping, MutableMapping +except ImportError: + from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + + class RLock: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +from collections import OrderedDict + +from .exceptions import InvalidHeader +from .packages import six +from .packages.six import iterkeys, itervalues + +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] + + +_Null = object() + + +class RecentlyUsedContainer(MutableMapping): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + ContainerCls = OrderedDict + + def __init__(self, maxsize=10, dispose_func=None): + self._maxsize = maxsize + self.dispose_func = dispose_func + + self._container = self.ContainerCls() + self.lock = RLock() + + def __getitem__(self, key): + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key, value): + evicted_value = _Null + with self.lock: + # Possibly evict the existing value of 'key' + evicted_value = self._container.get(key, _Null) + self._container[key] = value + + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: + self.dispose_func(evicted_value) + + def __delitem__(self, key): + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self): + with self.lock: + return len(self._container) + + def __iter__(self): + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) + + def clear(self): + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(itervalues(self._container)) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self): + with self.lock: + return list(iterkeys(self._container)) + + +class HTTPHeaderDict(MutableMapping): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = OrderedDict() + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key, val): + self._container[key.lower()] = [key, val] + return self._container[key.lower()] + + def __getitem__(self, key): + val = self._container[key.lower()] + return ", ".join(val[1:]) + + def __delitem__(self, key): + del self._container[key.lower()] + + def __contains__(self, key): + return key.lower() in self._container + + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, "keys"): + return False + if not isinstance(other, type(self)): + other = type(self)(other) + return dict((k.lower(), v) for k, v in self.itermerged()) == dict( + (k.lower(), v) for k, v in other.itermerged() + ) + + def __ne__(self, other): + return not self.__eq__(other) + + if six.PY2: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): + return len(self._container) + + def __iter__(self): + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def pop(self, key, default=__marker): + """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + """ + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): + try: + del self[key] + except KeyError: + pass + + def add(self, key, val): + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + """ + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + vals.append(val) + + def extend(self, *args, **kwargs): + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError( + "extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args)) + ) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): + for key in other.keys(): + self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) + + for key, value in kwargs.items(): + self.add(key, value) + + def getlist(self, key, default=__marker): + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + try: + vals = self._container[key.lower()] + except KeyError: + if default is self.__marker: + return [] + return default + else: + return vals[1:] + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) + + def _copy_from(self, other): + for key in other: + val = other.getlist(key) + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val + + def copy(self): + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self): + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self): + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ", ".join(val[1:]) + + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + obs_fold_continued_leaders = (" ", "\t") + headers = [] + + for line in message.headers: + if line.startswith(obs_fold_continued_leaders): + if not headers: + # We received a header line that starts with OWS as described + # in RFC-7230 S3.2.4. This indicates a multiline header, but + # there exists no previous header to which we can attach it. + raise InvalidHeader( + "Header continuation with no previous header: %s" % line + ) + else: + key, value = headers[-1] + headers[-1] = (key, value + " " + line.strip()) + continue + + key, value = line.split(":", 1) + headers.append((key, value.strip())) + + return cls(headers) diff --git a/lib/urllib3/_version.py b/lib/urllib3/_version.py new file mode 100644 index 0000000..e12dd0e --- /dev/null +++ b/lib/urllib3/_version.py @@ -0,0 +1,2 @@ +# This file is protected via CODEOWNERS +__version__ = "1.26.15" diff --git a/lib/urllib3/connection.py b/lib/urllib3/connection.py new file mode 100644 index 0000000..54b96b1 --- /dev/null +++ b/lib/urllib3/connection.py @@ -0,0 +1,572 @@ +from __future__ import absolute_import + +import datetime +import logging +import os +import re +import socket +import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from .packages import six +from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection +from .packages.six.moves.http_client import HTTPException # noqa: F401 +from .util.proxy import create_proxy_ssl_context + +try: # Compiled with SSL? + import ssl + + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None + + class BaseSSLError(BaseException): + pass + + +try: + # Python 3: not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: + # Python 2 + class ConnectionError(Exception): + pass + + +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + BrokenPipeError = BrokenPipeError +except NameError: # Python 2: + + class BrokenPipeError(Exception): + pass + + +from ._collections import HTTPHeaderDict # noqa (historical, removed in v2) +from ._version import __version__ +from .exceptions import ( + ConnectTimeoutError, + NewConnectionError, + SubjectAltNameWarning, + SystemTimeWarning, +) +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection +from .util.ssl_ import ( + assert_fingerprint, + create_urllib3_context, + is_ipaddress, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .util.ssl_match_hostname import CertificateError, match_hostname + +log = logging.getLogger(__name__) + +port_by_scheme = {"http": 80, "https": 443} + +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2022, 1, 1) + +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") + + +class HTTPConnection(_HTTPConnection, object): + """ + Based on :class:`http.client.HTTPConnection` but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass: + + .. code-block:: python + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port = port_by_scheme["http"] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] + + #: Whether this connection verifies the host's certificate. + is_verified = False + + #: Whether this proxy connection (if used) verifies the proxy host's + #: certificate. + proxy_is_verified = None + + def __init__(self, *args, **kw): + if not six.PY2: + kw.pop("strict", None) + + # Pre-set source_address. + self.source_address = kw.get("source_address") + + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop("socket_options", self.default_socket_options) + + # Proxy options provided by the user. + self.proxy = kw.pop("proxy", None) + self.proxy_config = kw.pop("proxy_config", None) + + _HTTPConnection.__init__(self, *args, **kw) + + @property + def host(self): + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip(".") + + @host.setter + def host(self, value): + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self): + """Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = connection.create_connection( + (self._dns_host, self.port), self.timeout, **extra_kw + ) + + except SocketTimeout: + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + return conn + + def _is_using_tunnel(self): + # Google App Engine's httplib does not define _tunnel_host + return getattr(self, "_tunnel_host", None) + + def _prepare_conn(self, conn): + self.sock = conn + if self._is_using_tunnel(): + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + def putrequest(self, method, url, *args, **kwargs): + """ """ + # Empty docstring because the indentation of CPython's implementation + # is broken but we don't want this method in our documentation. + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + "Method cannot contain non-token characters %r (found at least %r)" + % (method, match.group()) + ) + + return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) + + def putheader(self, header, *values): + """ """ + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + _HTTPConnection.putheader(self, header, *values) + elif six.ensure_str(header.lower()) not in SKIPPABLE_HEADERS: + raise ValueError( + "urllib3.util.SKIP_HEADER only supports '%s'" + % ("', '".join(map(str.title, sorted(SKIPPABLE_HEADERS))),) + ) + + def request(self, method, url, body=None, headers=None): + # Update the inner socket's timeout value to send the request. + # This only triggers if the connection is re-used. + if getattr(self, "sock", None) is not None: + self.sock.settimeout(self.timeout) + + if headers is None: + headers = {} + else: + # Avoid modifying the headers passed into .request() + headers = headers.copy() + if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): + headers["User-Agent"] = _get_default_user_agent() + super(HTTPConnection, self).request(method, url, body=body, headers=headers) + + def request_chunked(self, method, url, body=None, headers=None): + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + headers = headers or {} + header_keys = set([six.ensure_str(k.lower()) for k in headers]) + skip_accept_encoding = "accept-encoding" in header_keys + skip_host = "host" in header_keys + self.putrequest( + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host + ) + if "user-agent" not in header_keys: + self.putheader("User-Agent", _get_default_user_agent()) + for header, value in headers.items(): + self.putheader(header, value) + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") + self.endheaders() + + if body is not None: + stringish_types = six.string_types + (bytes,) + if isinstance(body, stringish_types): + body = (body,) + for chunk in body: + if not chunk: + continue + if not isinstance(chunk, bytes): + chunk = chunk.encode("utf8") + len_str = hex(len(chunk))[2:] + to_send = bytearray(len_str.encode()) + to_send += b"\r\n" + to_send += chunk + to_send += b"\r\n" + self.send(to_send) + + # After the if clause, to always have a closed body + self.send(b"0\r\n\r\n") + + +class HTTPSConnection(HTTPConnection): + """ + Many of the parameters to this constructor are passed to the underlying SSL + socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. + """ + + default_port = port_by_scheme["https"] + + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ca_cert_data = None + ssl_version = None + assert_fingerprint = None + tls_in_tls_required = False + + def __init__( + self, + host, + port=None, + key_file=None, + cert_file=None, + key_password=None, + strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, + server_hostname=None, + **kw + ): + + HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = "https" + + def set_cert( + self, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + ca_cert_data=None, + ): + """ + This method should only be called once, before the connection is used. + """ + # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also + # have an SSLContext object in which case we'll use its verify_mode. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + def connect(self): + # Add certificate verification + self.sock = conn = self._new_conn() + hostname = self.host + tls_in_tls = False + + if self._is_using_tunnel(): + if self.tls_in_tls_required: + self.sock = conn = self._connect_tls_proxy(hostname, conn) + tls_in_tls = True + + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + server_hostname = hostname + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + "System time is way off (before {0}). This will probably " + "lead to SSL verification errors" + ).format(RECENT_DATE), + SystemTimeWarning, + ) + + # Wrap socket using verification with the root certs in + # trusted_root_certs + default_ssl_context = False + if self.ssl_context is None: + default_ssl_context = True + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(self.ssl_version), + cert_reqs=resolve_cert_reqs(self.cert_reqs), + ) + + context = self.ssl_context + context.verify_mode = resolve_cert_reqs(self.cert_reqs) + + # Try to load OS default certs if none are given. + # Works well on Windows (requires Python3.4+) + if ( + not self.ca_certs + and not self.ca_cert_dir + and not self.ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + key_password=self.key_password, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + # If we're using all defaults and the connection + # is TLSv1 or TLSv1.1 we throw a DeprecationWarning + # for the host. + if ( + default_ssl_context + and self.ssl_version is None + and hasattr(self.sock, "version") + and self.sock.version() in {"TLSv1", "TLSv1.1"} + ): + warnings.warn( + "Negotiating TLSv1/TLSv1.1 by default is deprecated " + "and will be disabled in urllib3 v2.0.0. Connecting to " + "'%s' with '%s' can be enabled by explicitly opting-in " + "with 'ssl_version'" % (self.host, self.sock.version()), + DeprecationWarning, + ) + + if self.assert_fingerprint: + assert_fingerprint( + self.sock.getpeercert(binary_form=True), self.assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not getattr(context, "check_hostname", False) + and self.assert_hostname is not False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = self.sock.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, + ) + _match_hostname(cert, self.assert_hostname or server_hostname) + + self.is_verified = ( + context.verify_mode == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None + ) + + def _connect_tls_proxy(self, hostname, conn): + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + proxy_config = self.proxy_config + ssl_context = proxy_config.ssl_context + if ssl_context: + # If the user provided a proxy context, we assume CA and client + # certificates have already been set + return ssl_wrap_socket( + sock=conn, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + ssl_context = create_proxy_ssl_context( + self.ssl_version, + self.cert_reqs, + self.ca_certs, + self.ca_cert_dir, + self.ca_cert_data, + ) + + # If no cert was provided, use only the default options for server + # certificate validation + socket = ssl_wrap_socket( + sock=conn, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + if ssl_context.verify_mode != ssl.CERT_NONE and not getattr( + ssl_context, "check_hostname", False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = socket.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, + ) + _match_hostname(cert, hostname) + + self.proxy_is_verified = ssl_context.verify_mode == ssl.CERT_REQUIRED + return socket + + +def _match_hostname(cert, asserted_hostname): + # Our upstream implementation of ssl.match_hostname() + # only applies this normalization to IP addresses so it doesn't + # match DNS SANs so we do the same thing! + stripped_hostname = asserted_hostname.strip("u[]") + if is_ipaddress(stripped_hostname): + asserted_hostname = stripped_hostname + + try: + match_hostname(cert, asserted_hostname) + except CertificateError as e: + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert + raise + + +def _get_default_user_agent(): + return "python-urllib3/%s" % __version__ + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + + pass + + +if not ssl: + HTTPSConnection = DummyConnection # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection diff --git a/lib/urllib3/connectionpool.py b/lib/urllib3/connectionpool.py new file mode 100644 index 0000000..c23d736 --- /dev/null +++ b/lib/urllib3/connectionpool.py @@ -0,0 +1,1110 @@ +from __future__ import absolute_import + +import errno +import logging +import re +import socket +import sys +import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + VerifiedHTTPSConnection, + port_by_scheme, +) +from .exceptions import ( + ClosedPoolError, + EmptyPoolError, + HeaderParsingError, + HostChangedError, + InsecureRequestWarning, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, +) +from .packages import six +from .packages.six.moves import queue +from .request import RequestMethods +from .response import HTTPResponse +from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.queue import LifoQueue +from .util.request import set_file_position +from .util.response import assert_header_parsing +from .util.retry import Retry +from .util.ssl_match_hostname import CertificateError +from .util.timeout import Timeout +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import get_host, parse_url + +xrange = six.moves.xrange + +log = logging.getLogger(__name__) + +_Default = object() + + +# Pool objects +class ConnectionPool(object): + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + + .. note:: + ConnectionPool.urlopen() does not normalize or percent-encode target URIs + which is useful if your target server doesn't support percent-encoded + target URIs. + """ + + scheme = None + QueueCls = LifoQueue + + def __init__(self, host, port=None): + if not host: + raise LocationValueError("No host specified.") + + self.host = _normalize_host(host, scheme=self.scheme) + self._proxy_host = host.lower() + self.port = port + + def __str__(self): + return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + pass + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`http.client.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`http.client.HTTPConnection`. + + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`http.client.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.ProxyManager` + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.ProxyManager` + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = "http" + ConnectionCls = HTTPConnection + ResponseCls = HTTPResponse + + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + _proxy_config=None, + **conn_kw + ): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + self.strict = strict + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + self.proxy_config = _proxy_config + + # Fill the queue up so that doing get() on it will block properly + for _ in xrange(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config + + def _new_conn(self): + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) + + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + **self.conn_kw + ) + return conn + + def _get_conn(self, timeout=None): + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") + + except queue.Empty: + if self.block: + raise EmptyPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + if getattr(conn, "auto_open", 1) == 0: + # This is a proxied connection that has been mutated by + # http.client._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None + + return conn or self._new_conn() + + def _put_conn(self, conn): + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # This should never happen if self.block == True + log.warning( + "Connection pool is full, discarding connection: %s. Connection pool size: %s", + self.host, + self.pool.qsize(), + ) + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + pass + + def _prepare_proxy(self, conn): + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout): + """Helper that always returns a :class:`urllib3.util.Timeout`""" + if timeout is _Default: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout(self, err, url, timeout_value): + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if "timed out" in str(err) or "did not complete (read)" in str( + err + ): # Python < 2.7.4 + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + def _make_request( + self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw + ): + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param timeout: + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout) + + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # conn.request() calls http.client.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + try: + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + # Python 3 + pass + except IOError as e: + # Python 2 and macOS/Linux + # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE is needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + if e.errno not in { + errno.EPIPE, + errno.ESHUTDOWN, + errno.EPROTOTYPE, + }: + raise + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + # App Engine doesn't have a sock attr + if getattr(conn, "sock", None): + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout + ) + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) + + # Receive the response from the server + try: + try: + # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: + # Python 3 + try: + httplib_response = conn.getresponse() + except BaseException as e: + # Remove the TypeError from the exception chain in + # Python 3 (including for exceptions like SystemExit). + # Otherwise it looks like a bug in the code. + six.raise_from(e, None) + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # AppEngine doesn't have a version attr. + http_version = getattr(conn, "_http_vsn_str", "HTTP/?") + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + http_version, + httplib_response.status, + httplib_response.length, + ) + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 + log.warning( + "Failed to parse headers (url=%s): %s", + self._absolute_url(url), + hpe, + exc_info=True, + ) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + try: + while True: + conn = old_pool.get(block=False) + if conn: + conn.close() + + except queue.Empty: + pass # Done. + + def is_same_host(self, url): + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith("/"): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, host, port = get_host(url) + if host is not None: + host = _normalize_host(host, scheme=scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + assert_same_host=True, + timeout=_Default, + pool_timeout=None, + release_conn=None, + chunked=False, + body_pos=None, + **response_kw + ): + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When ``False``, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + + :param \\**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` + """ + + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = response_kw.get("preload_content", True) + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + url = six.ensure_str(_encode_target(url)) + else: + url = six.ensure_str(parsed_url.url) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] + release_this_conn = release_conn + + http_tunnel_required = connection_requires_http_tunnel( + self.proxy, self.proxy_config, destination_scheme + ) + + # Merge the proxy headers. Only done when not using HTTP CONNECT. We + # have to copy the headers dict so we can safely change it without those + # changes being reflected in anyone else's copy. + if not http_tunnel_required: + headers = headers.copy() + headers.update(self.proxy_headers) + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout + + is_new_proxy_conn = self.proxy is not None and not getattr( + conn, "sock", None + ) + if is_new_proxy_conn and http_tunnel_required: + self._prepare_proxy(conn) + + # Make the request on the httplib connection object. + httplib_response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + ) + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Pass method to Response for length checking + response_kw["request_method"] = method + + # Import httplib's response into our own wrapper object + response = self.ResponseCls.from_httplib( + httplib_response, + pool=self, + connection=response_conn, + retries=retries, + **response_kw + ) + + # Everything went great! + clean_exit = True + + except EmptyPoolError: + # Didn't get a connection from the pool, no need to clean up + clean_exit = True + release_this_conn = False + raise + + except ( + TimeoutError, + HTTPException, + SocketError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + + def _is_ssl_error_message_from_http_proxy(ssl_error): + # We're trying to detect the message 'WRONG_VERSION_NUMBER' but + # SSLErrors are kinda all over the place when it comes to the message, + # so we try to cover our bases here! + message = " ".join(re.split("[^a-z]", str(ssl_error).lower())) + return ( + "wrong version number" in message or "unknown protocol" in message + ) + + # Try to detect a common user error with proxies which is to + # set an HTTP proxy to be HTTPS when it should be 'http://' + # (ie {'http': 'http://proxy', 'https': 'https://proxy'}) + # Instead we add a nice error message and point to a URL. + if ( + isinstance(e, BaseSSLError) + and self.proxy + and _is_ssl_error_message_from_http_proxy(e) + and conn.proxy + and conn.proxy.scheme == "https" + ): + e = ProxyError( + "Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#https-proxy-error-http-proxy", + SSLError(e), + ) + elif isinstance(e, (BaseSSLError, CertificateError)): + e = SSLError(e) + elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError("Cannot connect to proxy.", e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError("Connection aborted.", e) + + retries = retries.increment( + method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] + ) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + conn = conn and conn.close() + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + method = "GET" + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.headers.get("Retry-After")) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, + url, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` + is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = "https" + ConnectionCls = HTTPSConnection + + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + ssl_version=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + **conn_kw + ): + + HTTPConnectionPool.__init__( + self, + host, + port, + strict, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw + ) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert( + key_file=self.key_file, + key_password=self.key_password, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + conn.ssl_version = self.ssl_version + return conn + + def _prepare_proxy(self, conn): + """ + Establishes a tunnel connection through HTTP CONNECT. + + Tunnel connection is established early because otherwise httplib would + improperly set Host: header to proxy's IP:port. + """ + + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + + if self.proxy.scheme == "https": + conn.tls_in_tls_required = True + + conn.connect() + + def _new_conn(self): + """ + Return a fresh :class:`http.client.HTTPSConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) + + actual_host = self.host + actual_port = self.port + if self.proxy is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + conn = self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + **self.conn_kw + ) + + return self._prepare_conn(conn) + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + super(HTTPSConnectionPool, self)._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if not getattr(conn, "sock", None): # AppEngine might not have `.sock` + conn.connect() + + if not conn.is_verified: + warnings.warn( + ( + "Unverified HTTPS request is being made to host '%s'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" % conn.host + ), + InsecureRequestWarning, + ) + + if getattr(conn, "proxy_is_verified", None) is False: + warnings.warn( + ( + "Unverified HTTPS connection done to an HTTPS proxy. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" + ), + InsecureRequestWarning, + ) + + +def connection_from_url(url, **kw): + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, host, port = get_host(url) + port = port or port_by_scheme.get(scheme, 80) + if scheme == "https": + return HTTPSConnectionPool(host, port=port, **kw) + else: + return HTTPConnectionPool(host, port=port, **kw) + + +def _normalize_host(host, scheme): + """ + Normalize hosts for comparisons and use with sockets. + """ + + host = normalize_host(host, scheme) + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + if host.startswith("[") and host.endswith("]"): + host = host[1:-1] + return host diff --git a/lib/urllib3/contrib/__init__.py b/lib/urllib3/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/urllib3/contrib/__pycache__/__init__.cpython-39.pyc b/lib/urllib3/contrib/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8b4a670e65133b4dee7dd8cd5e1c7307c15782f GIT binary patch literal 176 zcmYe~<>g`k0*|$lDIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o11>*(xTqIJKxa zCOEk$vmnMLwK%&ZzaS>Ppd`LHBQYhlD5fN}xWp*NCo?IgII|>Gw;(Y&J25@AIHt5H tCnqz>SU)*GucRn5Nk2Y5GcU6wK3=b&@)n0pZhlH>PO2Tqy3at&004wZEztk~ literal 0 HcmV?d00001 diff --git a/lib/urllib3/contrib/__pycache__/_appengine_environ.cpython-39.pyc b/lib/urllib3/contrib/__pycache__/_appengine_environ.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f41b3368235a08596ef25cbd79633d996924c6d6 GIT binary patch literal 1396 zcmb_c&2AJ&5bpW$?l86?wh$7Mg<8oWvIIL49D)#vV(=P}V1w2MBx|M7>{NND@oe{~ zyLS`&3J(HD4m<)cGFMK#LJmo)+s0nmIi{tq{++6?zprYF)zy?yd;0k%e2EzQ>K2PF zq{VO4&3!V!_89~a*1>)N5ybD=eh5p@p)rCkBs9iw1yUN9U>R0u>_E87Hdl`yrF)ey ze5znpBX6{t3qV?9Aq}^R16nLCw-xfOrr~`#5fVAdxzI{ZQQBVmjS#Rw>Fi<)Xz`f3 zSs`QDJJzH&c*iJeFxU*+bz$;al|`KwO@rQTyGDBi0nWERF&)$A-u#t6elU7vP@B?xe_vQqjl@+hi5D=nFi_(fY-sVP~`gdNA)Qp=-&1#T8 zTP_j7WkgwFdB_ixCT~6O&LHy2+QvM{va(h)=uMQG)YvQ4G&|!n167*LSbA5XA^VQq zduZ3TUcT%Po;(}$^Wm$(-m@3|HffG*rR3dTHY3k}8!FRwPt`ilij&zANzh(jlrKLe z&}H9=j9tA<^Gh=O+xi%JjN=raWAK d@nR$&Bzbu9pZnDT_5YjTK1~42xT=8b6(9`J0 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 literal 0 HcmV?d00001 diff --git a/lib/urllib3/contrib/__pycache__/ntlmpool.cpython-39.pyc b/lib/urllib3/contrib/__pycache__/ntlmpool.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23c4c48d1f0d6ca98778c5e238d24c3192eb695e GIT binary patch literal 3592 zcmb7HU2ogg873)`qGb6aw&Nsix}B}a3S)_!ru%}?ZZ#awG3KaQtRMjAf}nMdCE66p zoI^#iKw-erp;sHWe_#Q-=|zG4i2?lqy&`tEE8p#M$OdH3OG&o7^kx)z_@1x#yr0i| zAlH9QZ#yutr^MbrL4oy(7j&c}FFf`)6Z#&vFlde?9bo#;|na!dG{q1m-us~EFF zyKB2PXq%VAa@TR4f_B16x9V1PO_WY&ctzAk;2|tA`%-u3wl!YmGoNXEMmR4^?mRE; zX{)utZ>x>w?nCDH(vFDIpyj7Q)MiN>hqqZPj#3#M^isju!3h&GIBp3pq)Me9RjWHn z^#ptO{kylB5@MZo(ll9LTWiH!)Z1~~4n@5cch{1&o(#j_;Dfb*HmEf&Qa=cP5%7=R zzgsAYv0P?D@U=U5tmqSK{{hC|E>s%KQVcOJk;b2`R#YlqE7wgbYs@hruu)OgeH#EAFq{p?d zY9)n?@MO0O_tHu5Gb0*Sqoc_s^%gnd@?%XpY8`~oTpf{Va2=6oxaQ~DA>xvopE<6@ zEpC6Nxi+sAqZDBkxBR(gYOaI$n#~s}yuRtE!LO?f&rTP@kt7P{W^Y7c9I@vJHHb+% zVa3$=aN)%3MVJ=6a*KjbwJ4jWPZNFPp66c8yQ7Y;yPk?QqxMuiu#gmyg*z^AVY&)%j@2MDf{sTc2m z@^E9Pu}>Q!zbn{bEW3Vs!71s}QN$mNLZYmKT$|YjHOwAmz zG=14txqaNHuUn<%UgFDCeSw_!yjBQtJ?{(c)sxNj{U?}J`+F@JB>VS;I!@zczn!F> z>iB@7{Zy#*-u`ZIu&;tt+)4b_vELSIf0{0ngkPH`d_6glvk>C@coj*b*(Y^m65)pAt;Rx$DAsePGFvyr&V4hZXC135hF7r zZD^&n%sK+l5AEXQ8T97x&W}p^m_seIi9<0vn^~s|d~Q_m1SiY8lGz!B`y!to>BBPD zhE7(#uYJmfm8`-wzVLg5KfiKp$gj~~;2QOZ&ay0XVANvk@PEgui?F(&5W#JjXPQG|J0VO`z<<7t?*4vM<}d(jwCK@ z2+Q!L;}T^4J6VY@9~+>*g8WwtnV0y=)Ou4r#x^Q!cWp$VLFs?JA!p$H!3TROf|tr> z0b)fBqS`q{sVJ_qTl@mtZ>d` z3d@5(BR^oLF$hX=G~OY{BCoT)jA1>(fda??Qd7hl+6h;RM(sjN7@z#X+wvzYCcPD3X-Hl2JpY!hNTO(u_L@Y3DVzwm)g^ zY&5sLhg*Al8{1owN(s&aB0+~h$k&C3&0=JwduxjioRxrr~&jl)DP5Rb~% zia4s<^bgUv zRY>ZMC30s-(n%*b$uM~dLoNhV9WLEP3bM((_uLN=OYW67#&TV`^J6z|cKk3DQCqlk z4bhI%0CxLj=mXH5Vp~_W~;gq}RszWqdX-+qF122C6G0P5pjfIk} zo3E>-n!czzK;=cC^P*`NYaM-|Kx#vudu>|S``Rg)`oe1ytHqXE==RB1VNfDDtFU|=n7t%Q6y~!#O-Oa7HB|e9XpebD_P7Ok8V4s;; zz`ydS40I?p1+Sie5TStCBUzesiw}Z+(jerh-_FhWs(s zH=C;!s`r$HsP|RR8{Z;9*F5i8&krXvWzXYr3&XZ*b#R9(DysYuSQHZEvy&p;5@G0h zm%^YBmtvtX1rds(7IK=VQJZtNHGT5?SFvJsL4J&R66LU$_9VoT^cNs^aCe9RxhgLs zuiKy`xvI$GT$46v_dCU4=$b-E_vf)U`_S>%3h5~FhMbd literal 0 HcmV?d00001 diff --git a/lib/urllib3/contrib/__pycache__/pyopenssl.cpython-39.pyc b/lib/urllib3/contrib/__pycache__/pyopenssl.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6496f74b70326eed3e46834109b7e6489981ded5 GIT binary patch literal 15820 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 G{(k}cn{G=0 literal 0 HcmV?d00001 diff --git a/lib/urllib3/contrib/__pycache__/socks.cpython-39.pyc b/lib/urllib3/contrib/__pycache__/socks.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5152de68ce043fc48ea77600b7bc00ddc1899a8f GIT binary patch literal 5612 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 K$o|hj%m4rm;xyy{ literal 0 HcmV?d00001 diff --git a/lib/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-39.pyc b/lib/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..799ad3e10a559a8045342f8f9a106c2e6c733f01 GIT binary patch literal 10685 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 literal 0 HcmV?d00001 diff --git a/lib/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-39.pyc b/lib/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e394926ee676d6b89c47214db2c3def4a595014 GIT binary patch literal 9153 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 J`=7<~{{WjGjIjU! literal 0 HcmV?d00001 diff --git a/lib/urllib3/contrib/_securetransport/bindings.py b/lib/urllib3/contrib/_securetransport/bindings.py new file mode 100644 index 0000000..264d564 --- /dev/null +++ b/lib/urllib3/contrib/_securetransport/bindings.py @@ -0,0 +1,519 @@ +""" +This module uses ctypes to bind a whole bunch of functions and constants from +SecureTransport. The goal here is to provide the low-level API to +SecureTransport. These are essentially the C-level functions and constants, and +they're pretty gross to work with. + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import platform +from ctypes import ( + CDLL, + CFUNCTYPE, + POINTER, + c_bool, + c_byte, + c_char_p, + c_int32, + c_long, + c_size_t, + c_uint32, + c_ulong, + c_void_p, +) +from ctypes.util import find_library + +from ...packages.six import raise_from + +if platform.system() != "Darwin": + raise ImportError("Only macOS is supported") + +version = platform.mac_ver()[0] +version_info = tuple(map(int, version.split("."))) +if version_info < (10, 8): + raise OSError( + "Only OS X 10.8 and newer are supported, not %s.%s" + % (version_info[0], version_info[1]) + ) + + +def load_cdll(name, macos10_16_path): + """Loads a CDLL by name, falling back to known path on 10.16+""" + try: + # Big Sur is technically 11 but we use 10.16 due to the Big Sur + # beta being labeled as 10.16. + if version_info >= (10, 16): + path = macos10_16_path + else: + path = find_library(name) + if not path: + raise OSError # Caught and reraised as 'ImportError' + return CDLL(path, use_errno=True) + except OSError: + raise_from(ImportError("The library %s failed to load" % name), None) + + +Security = load_cdll( + "Security", "/System/Library/Frameworks/Security.framework/Security" +) +CoreFoundation = load_cdll( + "CoreFoundation", + "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", +) + + +Boolean = c_bool +CFIndex = c_long +CFStringEncoding = c_uint32 +CFData = c_void_p +CFString = c_void_p +CFArray = c_void_p +CFMutableArray = c_void_p +CFDictionary = c_void_p +CFError = c_void_p +CFType = c_void_p +CFTypeID = c_ulong + +CFTypeRef = POINTER(CFType) +CFAllocatorRef = c_void_p + +OSStatus = c_int32 + +CFDataRef = POINTER(CFData) +CFStringRef = POINTER(CFString) +CFArrayRef = POINTER(CFArray) +CFMutableArrayRef = POINTER(CFMutableArray) +CFDictionaryRef = POINTER(CFDictionary) +CFArrayCallBacks = c_void_p +CFDictionaryKeyCallBacks = c_void_p +CFDictionaryValueCallBacks = c_void_p + +SecCertificateRef = POINTER(c_void_p) +SecExternalFormat = c_uint32 +SecExternalItemType = c_uint32 +SecIdentityRef = POINTER(c_void_p) +SecItemImportExportFlags = c_uint32 +SecItemImportExportKeyParameters = c_void_p +SecKeychainRef = POINTER(c_void_p) +SSLProtocol = c_uint32 +SSLCipherSuite = c_uint32 +SSLContextRef = POINTER(c_void_p) +SecTrustRef = POINTER(c_void_p) +SSLConnectionRef = c_uint32 +SecTrustResultType = c_uint32 +SecTrustOptionFlags = c_uint32 +SSLProtocolSide = c_uint32 +SSLConnectionType = c_uint32 +SSLSessionOption = c_uint32 + + +try: + Security.SecItemImport.argtypes = [ + CFDataRef, + CFStringRef, + POINTER(SecExternalFormat), + POINTER(SecExternalItemType), + SecItemImportExportFlags, + POINTER(SecItemImportExportKeyParameters), + SecKeychainRef, + POINTER(CFArrayRef), + ] + Security.SecItemImport.restype = OSStatus + + Security.SecCertificateGetTypeID.argtypes = [] + Security.SecCertificateGetTypeID.restype = CFTypeID + + Security.SecIdentityGetTypeID.argtypes = [] + Security.SecIdentityGetTypeID.restype = CFTypeID + + Security.SecKeyGetTypeID.argtypes = [] + Security.SecKeyGetTypeID.restype = CFTypeID + + Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef] + Security.SecCertificateCreateWithData.restype = SecCertificateRef + + Security.SecCertificateCopyData.argtypes = [SecCertificateRef] + Security.SecCertificateCopyData.restype = CFDataRef + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SecIdentityCreateWithCertificate.argtypes = [ + CFTypeRef, + SecCertificateRef, + POINTER(SecIdentityRef), + ] + Security.SecIdentityCreateWithCertificate.restype = OSStatus + + Security.SecKeychainCreate.argtypes = [ + c_char_p, + c_uint32, + c_void_p, + Boolean, + c_void_p, + POINTER(SecKeychainRef), + ] + Security.SecKeychainCreate.restype = OSStatus + + Security.SecKeychainDelete.argtypes = [SecKeychainRef] + Security.SecKeychainDelete.restype = OSStatus + + Security.SecPKCS12Import.argtypes = [ + CFDataRef, + CFDictionaryRef, + POINTER(CFArrayRef), + ] + Security.SecPKCS12Import.restype = OSStatus + + SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE( + OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t) + ) + + Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc] + Security.SSLSetIOFuncs.restype = OSStatus + + Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerID.restype = OSStatus + + Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetCertificate.restype = OSStatus + + Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean] + Security.SSLSetCertificateAuthorities.restype = OSStatus + + Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef] + Security.SSLSetConnection.restype = OSStatus + + Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerDomainName.restype = OSStatus + + Security.SSLHandshake.argtypes = [SSLContextRef] + Security.SSLHandshake.restype = OSStatus + + Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLRead.restype = OSStatus + + Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLWrite.restype = OSStatus + + Security.SSLClose.argtypes = [SSLContextRef] + Security.SSLClose.restype = OSStatus + + Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberSupportedCiphers.restype = OSStatus + + Security.SSLGetSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetSupportedCiphers.restype = OSStatus + + Security.SSLSetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + c_size_t, + ] + Security.SSLSetEnabledCiphers.restype = OSStatus + + Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberEnabledCiphers.restype = OSStatus + + Security.SSLGetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetEnabledCiphers.restype = OSStatus + + Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)] + Security.SSLGetNegotiatedCipher.restype = OSStatus + + Security.SSLGetNegotiatedProtocolVersion.argtypes = [ + SSLContextRef, + POINTER(SSLProtocol), + ] + Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus + + Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)] + Security.SSLCopyPeerTrust.restype = OSStatus + + Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] + Security.SecTrustSetAnchorCertificates.restype = OSStatus + + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean] + Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus + + Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)] + Security.SecTrustEvaluate.restype = OSStatus + + Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef] + Security.SecTrustGetCertificateCount.restype = CFIndex + + Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex] + Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef + + Security.SSLCreateContext.argtypes = [ + CFAllocatorRef, + SSLProtocolSide, + SSLConnectionType, + ] + Security.SSLCreateContext.restype = SSLContextRef + + Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean] + Security.SSLSetSessionOption.restype = OSStatus + + Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMin.restype = OSStatus + + Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMax.restype = OSStatus + + try: + Security.SSLSetALPNProtocols.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetALPNProtocols.restype = OSStatus + except AttributeError: + # Supported only in 10.12+ + pass + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SSLReadFunc = SSLReadFunc + Security.SSLWriteFunc = SSLWriteFunc + Security.SSLContextRef = SSLContextRef + Security.SSLProtocol = SSLProtocol + Security.SSLCipherSuite = SSLCipherSuite + Security.SecIdentityRef = SecIdentityRef + Security.SecKeychainRef = SecKeychainRef + Security.SecTrustRef = SecTrustRef + Security.SecTrustResultType = SecTrustResultType + Security.SecExternalFormat = SecExternalFormat + Security.OSStatus = OSStatus + + Security.kSecImportExportPassphrase = CFStringRef.in_dll( + Security, "kSecImportExportPassphrase" + ) + Security.kSecImportItemIdentity = CFStringRef.in_dll( + Security, "kSecImportItemIdentity" + ) + + # CoreFoundation time! + CoreFoundation.CFRetain.argtypes = [CFTypeRef] + CoreFoundation.CFRetain.restype = CFTypeRef + + CoreFoundation.CFRelease.argtypes = [CFTypeRef] + CoreFoundation.CFRelease.restype = None + + CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef] + CoreFoundation.CFGetTypeID.restype = CFTypeID + + CoreFoundation.CFStringCreateWithCString.argtypes = [ + CFAllocatorRef, + c_char_p, + CFStringEncoding, + ] + CoreFoundation.CFStringCreateWithCString.restype = CFStringRef + + CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding] + CoreFoundation.CFStringGetCStringPtr.restype = c_char_p + + CoreFoundation.CFStringGetCString.argtypes = [ + CFStringRef, + c_char_p, + CFIndex, + CFStringEncoding, + ] + CoreFoundation.CFStringGetCString.restype = c_bool + + CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex] + CoreFoundation.CFDataCreate.restype = CFDataRef + + CoreFoundation.CFDataGetLength.argtypes = [CFDataRef] + CoreFoundation.CFDataGetLength.restype = CFIndex + + CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef] + CoreFoundation.CFDataGetBytePtr.restype = c_void_p + + CoreFoundation.CFDictionaryCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + POINTER(CFTypeRef), + CFIndex, + CFDictionaryKeyCallBacks, + CFDictionaryValueCallBacks, + ] + CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef + + CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef] + CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef + + CoreFoundation.CFArrayCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreate.restype = CFArrayRef + + CoreFoundation.CFArrayCreateMutable.argtypes = [ + CFAllocatorRef, + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef + + CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p] + CoreFoundation.CFArrayAppendValue.restype = None + + CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef] + CoreFoundation.CFArrayGetCount.restype = CFIndex + + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex] + CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p + + CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( + CoreFoundation, "kCFAllocatorDefault" + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeArrayCallBacks" + ) + CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryKeyCallBacks" + ) + CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryValueCallBacks" + ) + + CoreFoundation.CFTypeRef = CFTypeRef + CoreFoundation.CFArrayRef = CFArrayRef + CoreFoundation.CFStringRef = CFStringRef + CoreFoundation.CFDictionaryRef = CFDictionaryRef + +except (AttributeError): + raise ImportError("Error initializing ctypes") + + +class CFConst(object): + """ + A class object that acts as essentially a namespace for CoreFoundation + constants. + """ + + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) + + +class SecurityConst(object): + """ + A class object that acts as essentially a namespace for Security constants. + """ + + kSSLSessionOptionBreakOnServerAuth = 0 + + kSSLProtocol2 = 1 + kSSLProtocol3 = 2 + kTLSProtocol1 = 4 + kTLSProtocol11 = 7 + kTLSProtocol12 = 8 + # SecureTransport does not support TLS 1.3 even if there's a constant for it + kTLSProtocol13 = 10 + kTLSProtocolMaxSupported = 999 + + kSSLClientSide = 1 + kSSLStreamType = 0 + + kSecFormatPEMSequence = 10 + + kSecTrustResultInvalid = 0 + kSecTrustResultProceed = 1 + # This gap is present on purpose: this was kSecTrustResultConfirm, which + # is deprecated. + kSecTrustResultDeny = 3 + kSecTrustResultUnspecified = 4 + kSecTrustResultRecoverableTrustFailure = 5 + kSecTrustResultFatalTrustFailure = 6 + kSecTrustResultOtherError = 7 + + errSSLProtocol = -9800 + errSSLWouldBlock = -9803 + errSSLClosedGraceful = -9805 + errSSLClosedNoNotify = -9816 + errSSLClosedAbort = -9806 + + errSSLXCertChainInvalid = -9807 + errSSLCrypto = -9809 + errSSLInternal = -9810 + errSSLCertExpired = -9814 + errSSLCertNotYetValid = -9815 + errSSLUnknownRootCert = -9812 + errSSLNoRootCert = -9813 + errSSLHostNameMismatch = -9843 + errSSLPeerHandshakeFail = -9824 + errSSLPeerUserCancelled = -9839 + errSSLWeakPeerEphemeralDHKey = -9850 + errSSLServerAuthCompleted = -9841 + errSSLRecordOverflow = -9847 + + errSecVerifyFailed = -67808 + errSecNoTrustSettings = -25263 + errSecItemNotFound = -25300 + errSecInvalidTrustSettings = -25262 + + # Cipher suites. We only pick the ones our default cipher string allows. + # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + TLS_AES_128_GCM_SHA256 = 0x1301 + TLS_AES_256_GCM_SHA384 = 0x1302 + TLS_AES_128_CCM_8_SHA256 = 0x1305 + TLS_AES_128_CCM_SHA256 = 0x1304 diff --git a/lib/urllib3/contrib/_securetransport/low_level.py b/lib/urllib3/contrib/_securetransport/low_level.py new file mode 100644 index 0000000..fa0b245 --- /dev/null +++ b/lib/urllib3/contrib/_securetransport/low_level.py @@ -0,0 +1,397 @@ +""" +Low-level helpers for the SecureTransport bindings. + +These are Python functions that are not directly related to the high-level APIs +but are necessary to get them to work. They include a whole bunch of low-level +CoreFoundation messing about and memory management. The concerns in this module +are almost entirely about trying to avoid memory leaks and providing +appropriate and useful assistance to the higher-level code. +""" +import base64 +import ctypes +import itertools +import os +import re +import ssl +import struct +import tempfile + +from .bindings import CFConst, CoreFoundation, Security + +# This regular expression is used to grab PEM data out of a PEM bundle. +_PEM_CERTS_RE = re.compile( + b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL +) + + +def _cf_data_from_bytes(bytestring): + """ + Given a bytestring, create a CFData object from it. This CFData object must + be CFReleased by the caller. + """ + return CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) + ) + + +def _cf_dictionary_from_tuples(tuples): + """ + Given a list of Python tuples, create an associated CFDictionary. + """ + dictionary_size = len(tuples) + + # We need to get the dictionary keys and values out in the same order. + keys = (t[0] for t in tuples) + values = (t[1] for t in tuples) + cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) + cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) + + return CoreFoundation.CFDictionaryCreate( + CoreFoundation.kCFAllocatorDefault, + cf_keys, + cf_values, + dictionary_size, + CoreFoundation.kCFTypeDictionaryKeyCallBacks, + CoreFoundation.kCFTypeDictionaryValueCallBacks, + ) + + +def _cfstr(py_bstr): + """ + Given a Python binary data, create a CFString. + The string must be CFReleased by the caller. + """ + c_str = ctypes.c_char_p(py_bstr) + cf_str = CoreFoundation.CFStringCreateWithCString( + CoreFoundation.kCFAllocatorDefault, + c_str, + CFConst.kCFStringEncodingUTF8, + ) + return cf_str + + +def _create_cfstring_array(lst): + """ + Given a list of Python binary data, create an associated CFMutableArray. + The array must be CFReleased by the caller. + + Raises an ssl.SSLError on failure. + """ + cf_arr = None + try: + cf_arr = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cf_arr: + raise MemoryError("Unable to allocate memory!") + for item in lst: + cf_str = _cfstr(item) + if not cf_str: + raise MemoryError("Unable to allocate memory!") + try: + CoreFoundation.CFArrayAppendValue(cf_arr, cf_str) + finally: + CoreFoundation.CFRelease(cf_str) + except BaseException as e: + if cf_arr: + CoreFoundation.CFRelease(cf_arr) + raise ssl.SSLError("Unable to allocate array: %s" % (e,)) + return cf_arr + + +def _cf_string_to_unicode(value): + """ + Creates a Unicode string from a CFString object. Used entirely for error + reporting. + + Yes, it annoys me quite a lot that this function is this complex. + """ + value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) + + string = CoreFoundation.CFStringGetCStringPtr( + value_as_void_p, CFConst.kCFStringEncodingUTF8 + ) + if string is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8 + ) + if not result: + raise OSError("Error copying C string from CFStringRef") + string = buffer.value + if string is not None: + string = string.decode("utf-8") + return string + + +def _assert_no_error(error, exception_class=None): + """ + Checks the return code and throws an exception if there is an error to + report + """ + if error == 0: + return + + cf_error_string = Security.SecCopyErrorMessageString(error, None) + output = _cf_string_to_unicode(cf_error_string) + CoreFoundation.CFRelease(cf_error_string) + + if output is None or output == u"": + output = u"OSStatus %s" % error + + if exception_class is None: + exception_class = ssl.SSLError + + raise exception_class(output) + + +def _cert_array_from_pem(pem_bundle): + """ + Given a bundle of certs in PEM format, turns them into a CFArray of certs + that can be used to validate a cert chain. + """ + # Normalize the PEM bundle's line endings. + pem_bundle = pem_bundle.replace(b"\r\n", b"\n") + + der_certs = [ + base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle) + ] + if not der_certs: + raise ssl.SSLError("No root certificates specified") + + cert_array = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cert_array: + raise ssl.SSLError("Unable to allocate memory!") + + try: + for der_bytes in der_certs: + certdata = _cf_data_from_bytes(der_bytes) + if not certdata: + raise ssl.SSLError("Unable to allocate memory!") + cert = Security.SecCertificateCreateWithData( + CoreFoundation.kCFAllocatorDefault, certdata + ) + CoreFoundation.CFRelease(certdata) + if not cert: + raise ssl.SSLError("Unable to build cert object!") + + CoreFoundation.CFArrayAppendValue(cert_array, cert) + CoreFoundation.CFRelease(cert) + except Exception: + # We need to free the array before the exception bubbles further. + # We only want to do that if an error occurs: otherwise, the caller + # should free. + CoreFoundation.CFRelease(cert_array) + raise + + return cert_array + + +def _is_cert(item): + """ + Returns True if a given CFTypeRef is a certificate. + """ + expected = Security.SecCertificateGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _is_identity(item): + """ + Returns True if a given CFTypeRef is an identity. + """ + expected = Security.SecIdentityGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _temporary_keychain(): + """ + This function creates a temporary Mac keychain that we can use to work with + credentials. This keychain uses a one-time password and a temporary file to + store the data. We expect to have one keychain per socket. The returned + SecKeychainRef must be freed by the caller, including calling + SecKeychainDelete. + + Returns a tuple of the SecKeychainRef and the path to the temporary + directory that contains it. + """ + # Unfortunately, SecKeychainCreate requires a path to a keychain. This + # means we cannot use mkstemp to use a generic temporary file. Instead, + # we're going to create a temporary directory and a filename to use there. + # This filename will be 8 random bytes expanded into base64. We also need + # some random bytes to password-protect the keychain we're creating, so we + # ask for 40 random bytes. + random_bytes = os.urandom(40) + filename = base64.b16encode(random_bytes[:8]).decode("utf-8") + password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 + tempdirectory = tempfile.mkdtemp() + + keychain_path = os.path.join(tempdirectory, filename).encode("utf-8") + + # We now want to create the keychain itself. + keychain = Security.SecKeychainRef() + status = Security.SecKeychainCreate( + keychain_path, len(password), password, False, None, ctypes.byref(keychain) + ) + _assert_no_error(status) + + # Having created the keychain, we want to pass it off to the caller. + return keychain, tempdirectory + + +def _load_items_from_file(keychain, path): + """ + Given a single file, loads all the trust objects from it into arrays and + the keychain. + Returns a tuple of lists: the first list is a list of identities, the + second a list of certs. + """ + certificates = [] + identities = [] + result_array = None + + with open(path, "rb") as f: + raw_filedata = f.read() + + try: + filedata = CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata) + ) + result_array = CoreFoundation.CFArrayRef() + result = Security.SecItemImport( + filedata, # cert data + None, # Filename, leaving it out for now + None, # What the type of the file is, we don't care + None, # what's in the file, we don't care + 0, # import flags + None, # key params, can include passphrase in the future + keychain, # The keychain to insert into + ctypes.byref(result_array), # Results + ) + _assert_no_error(result) + + # A CFArray is not very useful to us as an intermediary + # representation, so we are going to extract the objects we want + # and then free the array. We don't need to keep hold of keys: the + # keychain already has them! + result_count = CoreFoundation.CFArrayGetCount(result_array) + for index in range(result_count): + item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index) + item = ctypes.cast(item, CoreFoundation.CFTypeRef) + + if _is_cert(item): + CoreFoundation.CFRetain(item) + certificates.append(item) + elif _is_identity(item): + CoreFoundation.CFRetain(item) + identities.append(item) + finally: + if result_array: + CoreFoundation.CFRelease(result_array) + + CoreFoundation.CFRelease(filedata) + + return (identities, certificates) + + +def _load_client_cert_chain(keychain, *paths): + """ + Load certificates and maybe keys from a number of files. Has the end goal + of returning a CFArray containing one SecIdentityRef, and then zero or more + SecCertificateRef objects, suitable for use as a client certificate trust + chain. + """ + # Ok, the strategy. + # + # This relies on knowing that macOS will not give you a SecIdentityRef + # unless you have imported a key into a keychain. This is a somewhat + # artificial limitation of macOS (for example, it doesn't necessarily + # affect iOS), but there is nothing inside Security.framework that lets you + # get a SecIdentityRef without having a key in a keychain. + # + # So the policy here is we take all the files and iterate them in order. + # Each one will use SecItemImport to have one or more objects loaded from + # it. We will also point at a keychain that macOS can use to work with the + # private key. + # + # Once we have all the objects, we'll check what we actually have. If we + # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, + # we'll take the first certificate (which we assume to be our leaf) and + # ask the keychain to give us a SecIdentityRef with that cert's associated + # key. + # + # We'll then return a CFArray containing the trust chain: one + # SecIdentityRef and then zero-or-more SecCertificateRef objects. The + # responsibility for freeing this CFArray will be with the caller. This + # CFArray must remain alive for the entire connection, so in practice it + # will be stored with a single SSLSocket, along with the reference to the + # keychain. + certificates = [] + identities = [] + + # Filter out bad paths. + paths = (path for path in paths if path) + + try: + for file_path in paths: + new_identities, new_certs = _load_items_from_file(keychain, file_path) + identities.extend(new_identities) + certificates.extend(new_certs) + + # Ok, we have everything. The question is: do we have an identity? If + # not, we want to grab one from the first cert we have. + if not identities: + new_identity = Security.SecIdentityRef() + status = Security.SecIdentityCreateWithCertificate( + keychain, certificates[0], ctypes.byref(new_identity) + ) + _assert_no_error(status) + identities.append(new_identity) + + # We now want to release the original certificate, as we no longer + # need it. + CoreFoundation.CFRelease(certificates.pop(0)) + + # We now need to build a new CFArray that holds the trust chain. + trust_chain = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + for item in itertools.chain(identities, certificates): + # ArrayAppendValue does a CFRetain on the item. That's fine, + # because the finally block will release our other refs to them. + CoreFoundation.CFArrayAppendValue(trust_chain, item) + + return trust_chain + finally: + for obj in itertools.chain(identities, certificates): + CoreFoundation.CFRelease(obj) + + +TLS_PROTOCOL_VERSIONS = { + "SSLv2": (0, 2), + "SSLv3": (3, 0), + "TLSv1": (3, 1), + "TLSv1.1": (3, 2), + "TLSv1.2": (3, 3), +} + + +def _build_tls_unknown_ca_alert(version): + """ + Builds a TLS alert record for an unknown CA. + """ + ver_maj, ver_min = TLS_PROTOCOL_VERSIONS[version] + severity_fatal = 0x02 + description_unknown_ca = 0x30 + msg = struct.pack(">BB", severity_fatal, description_unknown_ca) + msg_len = len(msg) + record_type_alert = 0x15 + record = struct.pack(">BBBH", record_type_alert, ver_maj, ver_min, msg_len) + msg + return record diff --git a/lib/urllib3/contrib/appengine.py b/lib/urllib3/contrib/appengine.py new file mode 100644 index 0000000..a5a6d91 --- /dev/null +++ b/lib/urllib3/contrib/appengine.py @@ -0,0 +1,314 @@ +""" +This module provides a pool manager that uses Google App Engine's +`URLFetch Service `_. + +Example usage:: + + from urllib3 import PoolManager + from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox + + if is_appengine_sandbox(): + # AppEngineManager uses AppEngine's URLFetch API behind the scenes + http = AppEngineManager() + else: + # PoolManager uses a socket-level API behind the scenes + http = PoolManager() + + r = http.request('GET', 'https://google.com/') + +There are `limitations `_ to the URLFetch service and it may not be +the best choice for your application. There are three options for using +urllib3 on Google App Engine: + +1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is + cost-effective in many circumstances as long as your usage is within the + limitations. +2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. + Sockets also have `limitations and restrictions + `_ and have a lower free quota than URLFetch. + To use sockets, be sure to specify the following in your ``app.yaml``:: + + env_variables: + GAE_USE_SOCKETS_HTTPLIB : 'true' + +3. If you are using `App Engine Flexible +`_, you can use the standard +:class:`PoolManager` without any configuration or special environment variables. +""" + +from __future__ import absolute_import + +import io +import logging +import warnings + +from ..exceptions import ( + HTTPError, + HTTPWarning, + MaxRetryError, + ProtocolError, + SSLError, + TimeoutError, +) +from ..packages.six.moves.urllib.parse import urljoin +from ..request import RequestMethods +from ..response import HTTPResponse +from ..util.retry import Retry +from ..util.timeout import Timeout +from . import _appengine_environ + +try: + from google.appengine.api import urlfetch +except ImportError: + urlfetch = None + + +log = logging.getLogger(__name__) + + +class AppEnginePlatformWarning(HTTPWarning): + pass + + +class AppEnginePlatformError(HTTPError): + pass + + +class AppEngineManager(RequestMethods): + """ + Connection manager for Google App Engine sandbox applications. + + This manager uses the URLFetch service directly instead of using the + emulated httplib, and is subject to URLFetch limitations as described in + the App Engine documentation `here + `_. + + Notably it will raise an :class:`AppEnginePlatformError` if: + * URLFetch is not available. + * If you attempt to use this on App Engine Flexible, as full socket + support is available. + * If a request size is more than 10 megabytes. + * If a response size is more than 32 megabytes. + * If you use an unsupported request method such as OPTIONS. + + Beyond those cases, it will raise normal urllib3 errors. + """ + + def __init__( + self, + headers=None, + retries=None, + validate_certificate=True, + urlfetch_retries=True, + ): + if not urlfetch: + raise AppEnginePlatformError( + "URLFetch is not available in this environment." + ) + + warnings.warn( + "urllib3 is using URLFetch on Google App Engine sandbox instead " + "of sockets. To use sockets directly instead of URLFetch see " + "https://urllib3.readthedocs.io/en/1.26.x/reference/urllib3.contrib.html.", + AppEnginePlatformWarning, + ) + + RequestMethods.__init__(self, headers) + self.validate_certificate = validate_certificate + self.urlfetch_retries = urlfetch_retries + + self.retries = retries or Retry.DEFAULT + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Return False to re-raise any potential exceptions + return False + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw + ): + + retries = self._get_retries(retries, redirect) + + try: + follow_redirects = redirect and retries.redirect != 0 and retries.total + response = urlfetch.fetch( + url, + payload=body, + method=method, + headers=headers or {}, + allow_truncated=False, + follow_redirects=self.urlfetch_retries and follow_redirects, + deadline=self._get_absolute_timeout(timeout), + validate_certificate=self.validate_certificate, + ) + except urlfetch.DeadlineExceededError as e: + raise TimeoutError(self, e) + + except urlfetch.InvalidURLError as e: + if "too large" in str(e): + raise AppEnginePlatformError( + "URLFetch request too large, URLFetch only " + "supports requests up to 10mb in size.", + e, + ) + raise ProtocolError(e) + + except urlfetch.DownloadError as e: + if "Too many redirects" in str(e): + raise MaxRetryError(self, url, reason=e) + raise ProtocolError(e) + + except urlfetch.ResponseTooLargeError as e: + raise AppEnginePlatformError( + "URLFetch response too large, URLFetch only supports" + "responses up to 32mb in size.", + e, + ) + + except urlfetch.SSLCertificateError as e: + raise SSLError(e) + + except urlfetch.InvalidMethodError as e: + raise AppEnginePlatformError( + "URLFetch does not support method: %s" % method, e + ) + + http_response = self._urlfetch_response_to_http_response( + response, retries=retries, **response_kw + ) + + # Handle redirect? + redirect_location = redirect and http_response.get_redirect_location() + if redirect_location: + # Check for redirect response + if self.urlfetch_retries and retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + else: + if http_response.status == 303: + method = "GET" + + try: + retries = retries.increment( + method, url, response=http_response, _pool=self + ) + except MaxRetryError: + if retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + return http_response + + retries.sleep_for_retry(http_response) + log.debug("Redirecting %s -> %s", url, redirect_location) + redirect_url = urljoin(url, redirect_location) + return self.urlopen( + method, + redirect_url, + body, + headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(http_response.headers.get("Retry-After")) + if retries.is_retry(method, http_response.status, has_retry_after): + retries = retries.increment(method, url, response=http_response, _pool=self) + log.debug("Retry: %s", url) + retries.sleep(http_response) + return self.urlopen( + method, + url, + body=body, + headers=headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + return http_response + + def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): + + if is_prod_appengine(): + # Production GAE handles deflate encoding automatically, but does + # not remove the encoding header. + content_encoding = urlfetch_resp.headers.get("content-encoding") + + if content_encoding == "deflate": + del urlfetch_resp.headers["content-encoding"] + + transfer_encoding = urlfetch_resp.headers.get("transfer-encoding") + # We have a full response's content, + # so let's make sure we don't report ourselves as chunked data. + if transfer_encoding == "chunked": + encodings = transfer_encoding.split(",") + encodings.remove("chunked") + urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings) + + original_response = HTTPResponse( + # In order for decoding to work, we must present the content as + # a file-like object. + body=io.BytesIO(urlfetch_resp.content), + msg=urlfetch_resp.header_msg, + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + **response_kw + ) + + return HTTPResponse( + body=io.BytesIO(urlfetch_resp.content), + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + original_response=original_response, + **response_kw + ) + + def _get_absolute_timeout(self, timeout): + if timeout is Timeout.DEFAULT_TIMEOUT: + return None # Defer to URLFetch's default. + if isinstance(timeout, Timeout): + if timeout._read is not None or timeout._connect is not None: + warnings.warn( + "URLFetch does not support granular timeout settings, " + "reverting to total or default URLFetch timeout.", + AppEnginePlatformWarning, + ) + return timeout.total + return timeout + + def _get_retries(self, retries, redirect): + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if retries.connect or retries.read or retries.redirect: + warnings.warn( + "URLFetch only supports total retries and does not " + "recognize connect, read, or redirect retry parameters.", + AppEnginePlatformWarning, + ) + + return retries + + +# Alias methods from _appengine_environ to maintain public API interface. + +is_appengine = _appengine_environ.is_appengine +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox +is_local_appengine = _appengine_environ.is_local_appengine +is_prod_appengine = _appengine_environ.is_prod_appengine +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/lib/urllib3/contrib/ntlmpool.py b/lib/urllib3/contrib/ntlmpool.py new file mode 100644 index 0000000..4716657 --- /dev/null +++ b/lib/urllib3/contrib/ntlmpool.py @@ -0,0 +1,130 @@ +""" +NTLM authenticating pool, contributed by erikcederstran + +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 +""" +from __future__ import absolute_import + +import warnings +from logging import getLogger + +from ntlm import ntlm + +from .. import HTTPSConnectionPool +from ..packages.six.moves.http_client import HTTPSConnection + +warnings.warn( + "The 'urllib3.contrib.ntlmpool' module is deprecated and will be removed " + "in urllib3 v2.0 release, urllib3 is not able to support it properly due " + "to reasons listed in issue: https://github.com/urllib3/urllib3/issues/2282. " + "If you are a user of this module please comment in the mentioned issue.", + DeprecationWarning, +) + +log = getLogger(__name__) + + +class NTLMConnectionPool(HTTPSConnectionPool): + """ + Implements an NTLM authentication version of an urllib3 connection pool + """ + + scheme = "https" + + def __init__(self, user, pw, authurl, *args, **kwargs): + """ + authurl is a random URL on the server that is protected by NTLM. + user is the Windows user, probably in the DOMAIN\\username format. + pw is the password for the user. + """ + super(NTLMConnectionPool, self).__init__(*args, **kwargs) + self.authurl = authurl + self.rawuser = user + user_parts = user.split("\\", 1) + self.domain = user_parts[0].upper() + self.user = user_parts[1] + self.pw = pw + + def _new_conn(self): + # Performs the NTLM handshake that secures the connection. The socket + # must be kept open while requests are performed. + self.num_connections += 1 + log.debug( + "Starting NTLM HTTPS connection no. %d: https://%s%s", + self.num_connections, + self.host, + self.authurl, + ) + + headers = {"Connection": "Keep-Alive"} + req_header = "Authorization" + resp_header = "www-authenticate" + + conn = HTTPSConnection(host=self.host, port=self.port) + + # Send negotiation message + headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( + self.rawuser + ) + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + reshdr = dict(res.headers) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", reshdr) + log.debug("Response data: %s [...]", res.read(100)) + + # Remove the reference to the socket, so that it can not be closed by + # the response object (we want to keep the socket open) + res.fp = None + + # Server should respond with a challenge message + auth_header_values = reshdr[resp_header].split(", ") + auth_header_value = None + for s in auth_header_values: + if s[:5] == "NTLM ": + auth_header_value = s[5:] + if auth_header_value is None: + raise Exception( + "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) + ) + + # Send authentication message + ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( + auth_header_value + ) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( + ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags + ) + headers[req_header] = "NTLM %s" % auth_msg + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", dict(res.headers)) + log.debug("Response data: %s [...]", res.read()[:100]) + if res.status != 200: + if res.status == 401: + raise Exception("Server rejected request: wrong username or password") + raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) + + res.fp = None + log.debug("Connection established") + return conn + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=3, + redirect=True, + assert_same_host=True, + ): + if headers is None: + headers = {} + headers["Connection"] = "Keep-Alive" + return super(NTLMConnectionPool, self).urlopen( + method, url, body, headers, retries, redirect, assert_same_host + ) diff --git a/lib/urllib3/contrib/pyopenssl.py b/lib/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000..1ed214b --- /dev/null +++ b/lib/urllib3/contrib/pyopenssl.py @@ -0,0 +1,518 @@ +""" +TLS with SNI_-support for Python 2. Follow these instructions if you would +like to verify TLS certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. + +This needs the following packages installed: + +* `pyOpenSSL`_ (tested with 16.0.0) +* `cryptography`_ (minimum 1.3.4, from pyopenssl) +* `idna`_ (minimum 2.0, from cryptography) + +However, pyopenssl depends on cryptography, which depends on idna, so while we +use all three directly here we end up having relatively few packages required. + +You can install them with the following command: + +.. code-block:: bash + + $ python -m pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this: + +.. code-block:: python + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna +""" +from __future__ import absolute_import + +import OpenSSL.crypto +import OpenSSL.SSL +from cryptography import x509 +from cryptography.hazmat.backends.openssl import backend as openssl_backend + +try: + from cryptography.x509 import UnsupportedExtension +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): + pass + + +from io import BytesIO +from socket import error as SocketError +from socket import timeout + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +import logging +import ssl +import sys +import warnings + +from .. import util +from ..packages import six +from ..util.ssl_ import PROTOCOL_TLS_CLIENT + +warnings.warn( + "'urllib3.contrib.pyopenssl' module is deprecated and will be removed " + "in a future release of urllib3 2.x. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2680", + category=DeprecationWarning, + stacklevel=2, +) + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works. +HAS_SNI = True + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions = { + util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, + PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): + _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3(): + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." + + _validate_dependencies_met() + + util.SSLContext = PyOpenSSLContext + util.ssl_.SSLContext = PyOpenSSLContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3(): + "Undo monkey-patching by :func:`inject_into_urllib3`." + + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met(): + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) + + +def _dnsname_to_stdlib(name): + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + + def idna_encode(name): + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + import idna + + try: + for prefix in [u"*.", u"."]: + if name.startswith(prefix): + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + # Don't send IPv6 addresses through the IDNA encoder. + if ":" in name: + return name + + name = idna_encode(name) + if name is None: + return None + elif sys.version_info >= (3, 0): + name = name.decode("utf-8") + return name + + +def get_subj_alt_name(peer_cert): + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + # Pass the cert to cryptography, which has much better APIs for this. + if hasattr(peer_cert, "to_cryptography"): + cert = peer_cert.to_cryptography() + else: + der = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, peer_cert) + cert = x509.load_der_x509_certificate(der, openssl_backend) + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket(object): + """API-compatibility wrapper for Python OpenSSL's Connection-class. + + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + """ + + def __init__(self, connection, socket, suppress_ragged_eofs=True): + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 + self._closed = False + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args, **kwargs): + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b"" + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") + else: + return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + else: + return data + + def recv_into(self, *args, **kwargs): + try: + return self.connection.recv_into(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return 0 + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") + else: + return self.recv_into(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + + def settimeout(self, timeout): + return self.socket.settimeout(timeout) + + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise timeout() + continue + except OpenSSL.SSL.SysCallError as e: + raise SocketError(str(e)) + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) + total_sent += sent + + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() + + def close(self): + if self._makefile_refs < 1: + try: + self._closed = True + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 + + if binary_form: + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) + + return { + "subject": ((("commonName", x509.get_subject().CN),),), + "subjectAltName": get_subj_alt_name(x509), + } + + def version(self): + return self.connection.get_protocol_version_name() + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + +else: # Platform-specific: Python 3 + makefile = backport_makefile + +WrappedSocket.makefile = makefile + + +class PyOpenSSLContext(object): + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + + def __init__(self, protocol): + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + + @property + def options(self): + return self._options + + @options.setter + def options(self, value): + self._options = value + self._ctx.set_options(value) + + @property + def verify_mode(self): + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value): + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) + + def set_default_verify_paths(self): + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers): + if isinstance(ciphers, six.text_type): + ciphers = ciphers.encode("utf-8") + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + if cafile is not None: + cafile = cafile.encode("utf-8") + if capath is not None: + capath = capath.encode("utf-8") + try: + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("unable to load trusted certificates: %r" % e) + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, six.binary_type): + password = password.encode("utf-8") + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) + + def set_alpn_protocols(self, protocols): + protocols = [six.ensure_binary(p) for p in protocols] + return self._ctx.set_alpn_protos(protocols) + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 + server_hostname = server_hostname.encode("utf-8") + + if server_hostname is not None: + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(sock, sock.gettimeout()): + raise timeout("select timed out") + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("bad handshake: %r" % e) + break + + return WrappedSocket(cnx, sock) + + +def _verify_callback(cnx, x509, err_no, err_depth, return_code): + return err_no == 0 diff --git a/lib/urllib3/contrib/securetransport.py b/lib/urllib3/contrib/securetransport.py new file mode 100644 index 0000000..6c46a3b --- /dev/null +++ b/lib/urllib3/contrib/securetransport.py @@ -0,0 +1,921 @@ +""" +SecureTranport support for urllib3 via ctypes. + +This makes platform-native TLS available to urllib3 users on macOS without the +use of a compiler. This is an important feature because the Python Package +Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL +that ships with macOS is not capable of doing TLSv1.2. The only way to resolve +this is to give macOS users an alternative solution to the problem, and that +solution is to use SecureTransport. + +We use ctypes here because this solution must not require a compiler. That's +because pip is not allowed to require a compiler either. + +This is not intended to be a seriously long-term solution to this problem. +The hope is that PEP 543 will eventually solve this issue for us, at which +point we can retire this contrib module. But in the short term, we need to +solve the impending tire fire that is Python on Mac without this kind of +contrib module. So...here we are. + +To use this module, simply import and inject it:: + + import urllib3.contrib.securetransport + urllib3.contrib.securetransport.inject_into_urllib3() + +Happy TLSing! + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + +.. code-block:: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import contextlib +import ctypes +import errno +import os.path +import shutil +import socket +import ssl +import struct +import threading +import weakref + +import six + +from .. import util +from ..util.ssl_ import PROTOCOL_TLS_CLIENT +from ._securetransport.bindings import CoreFoundation, Security, SecurityConst +from ._securetransport.low_level import ( + _assert_no_error, + _build_tls_unknown_ca_alert, + _cert_array_from_pem, + _create_cfstring_array, + _load_client_cert_chain, + _temporary_keychain, +) + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works +HAS_SNI = True + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + +# This dictionary is used by the read callback to obtain a handle to the +# calling wrapped socket. This is a pretty silly approach, but for now it'll +# do. I feel like I should be able to smuggle a handle to the wrapped socket +# directly in the SSLConnectionRef, but for now this approach will work I +# guess. +# +# We need to lock around this structure for inserts, but we don't do it for +# reads/writes in the callbacks. The reasoning here goes as follows: +# +# 1. It is not possible to call into the callbacks before the dictionary is +# populated, so once in the callback the id must be in the dictionary. +# 2. The callbacks don't mutate the dictionary, they only read from it, and +# so cannot conflict with any of the insertions. +# +# This is good: if we had to lock in the callbacks we'd drastically slow down +# the performance of this code. +_connection_refs = weakref.WeakValueDictionary() +_connection_ref_lock = threading.Lock() + +# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over +# for no better reason than we need *a* limit, and this one is right there. +SSL_WRITE_BLOCKSIZE = 16384 + +# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to +# individual cipher suites. We need to do this because this is how +# SecureTransport wants them. +CIPHER_SUITES = [ + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_AES_256_GCM_SHA384, + SecurityConst.TLS_AES_128_GCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_AES_128_CCM_8_SHA256, + SecurityConst.TLS_AES_128_CCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, +] + +# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +# TLSv1 to 1.2 are supported on macOS 10.8+ +_protocol_to_min_max = { + util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), + PROTOCOL_TLS_CLIENT: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), +} + +if hasattr(ssl, "PROTOCOL_SSLv2"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( + SecurityConst.kSSLProtocol2, + SecurityConst.kSSLProtocol2, + ) +if hasattr(ssl, "PROTOCOL_SSLv3"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( + SecurityConst.kSSLProtocol3, + SecurityConst.kSSLProtocol3, + ) +if hasattr(ssl, "PROTOCOL_TLSv1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( + SecurityConst.kTLSProtocol1, + SecurityConst.kTLSProtocol1, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( + SecurityConst.kTLSProtocol11, + SecurityConst.kTLSProtocol11, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_2"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( + SecurityConst.kTLSProtocol12, + SecurityConst.kTLSProtocol12, + ) + + +def inject_into_urllib3(): + """ + Monkey-patch urllib3 with SecureTransport-backed SSL-support. + """ + util.SSLContext = SecureTransportContext + util.ssl_.SSLContext = SecureTransportContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_SECURETRANSPORT = True + util.ssl_.IS_SECURETRANSPORT = True + + +def extract_from_urllib3(): + """ + Undo monkey-patching by :func:`inject_into_urllib3`. + """ + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_SECURETRANSPORT = False + util.ssl_.IS_SECURETRANSPORT = False + + +def _read_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport read callback. This is called by ST to request that data + be returned from the socket. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + requested_length = data_length_pointer[0] + + timeout = wrapped_socket.gettimeout() + error = None + read_count = 0 + + try: + while read_count < requested_length: + if timeout is None or timeout >= 0: + if not util.wait_for_read(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + + remaining = requested_length - read_count + buffer = (ctypes.c_char * remaining).from_address( + data_buffer + read_count + ) + chunk_size = base_socket.recv_into(buffer, remaining) + read_count += chunk_size + if not chunk_size: + if not read_count: + return SecurityConst.errSSLClosedGraceful + break + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = read_count + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = read_count + + if read_count != requested_length: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +def _write_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport write callback. This is called by ST to request that data + actually be sent on the network. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + bytes_to_write = data_length_pointer[0] + data = ctypes.string_at(data_buffer, bytes_to_write) + + timeout = wrapped_socket.gettimeout() + error = None + sent = 0 + + try: + while sent < bytes_to_write: + if timeout is None or timeout >= 0: + if not util.wait_for_write(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + chunk_sent = base_socket.send(data) + sent += chunk_sent + + # This has some needless copying here, but I'm not sure there's + # much value in optimising this data path. + data = data[chunk_sent:] + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = sent + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = sent + + if sent != bytes_to_write: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +# We need to keep these two objects references alive: if they get GC'd while +# in use then SecureTransport could attempt to call a function that is in freed +# memory. That would be...uh...bad. Yeah, that's the word. Bad. +_read_callback_pointer = Security.SSLReadFunc(_read_callback) +_write_callback_pointer = Security.SSLWriteFunc(_write_callback) + + +class WrappedSocket(object): + """ + API-compatibility wrapper for Python's OpenSSL wrapped socket object. + + Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage + collector of PyPy. + """ + + def __init__(self, socket): + self.socket = socket + self.context = None + self._makefile_refs = 0 + self._closed = False + self._exception = None + self._keychain = None + self._keychain_dir = None + self._client_cert_chain = None + + # We save off the previously-configured timeout and then set it to + # zero. This is done because we use select and friends to handle the + # timeouts, but if we leave the timeout set on the lower socket then + # Python will "kindly" call select on that socket again for us. Avoid + # that by forcing the timeout to zero. + self._timeout = self.socket.gettimeout() + self.socket.settimeout(0) + + @contextlib.contextmanager + def _raise_on_error(self): + """ + A context manager that can be used to wrap calls that do I/O from + SecureTransport. If any of the I/O callbacks hit an exception, this + context manager will correctly propagate the exception after the fact. + This avoids silently swallowing those exceptions. + + It also correctly forces the socket closed. + """ + self._exception = None + + # We explicitly don't catch around this yield because in the unlikely + # event that an exception was hit in the block we don't want to swallow + # it. + yield + if self._exception is not None: + exception, self._exception = self._exception, None + self.close() + raise exception + + def _set_ciphers(self): + """ + Sets up the allowed ciphers. By default this matches the set in + util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done + custom and doesn't allow changing at this time, mostly because parsing + OpenSSL cipher strings is going to be a freaking nightmare. + """ + ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) + result = Security.SSLSetEnabledCiphers( + self.context, ciphers, len(CIPHER_SUITES) + ) + _assert_no_error(result) + + def _set_alpn_protocols(self, protocols): + """ + Sets up the ALPN protocols on the context. + """ + if not protocols: + return + protocols_arr = _create_cfstring_array(protocols) + try: + result = Security.SSLSetALPNProtocols(self.context, protocols_arr) + _assert_no_error(result) + finally: + CoreFoundation.CFRelease(protocols_arr) + + def _custom_validate(self, verify, trust_bundle): + """ + Called when we have set custom validation. We do this in two cases: + first, when cert validation is entirely disabled; and second, when + using a custom trust DB. + Raises an SSLError if the connection is not trusted. + """ + # If we disabled cert validation, just say: cool. + if not verify: + return + + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed, + ) + try: + trust_result = self._evaluate_trust(trust_bundle) + if trust_result in successes: + return + reason = "error code: %d" % (trust_result,) + except Exception as e: + # Do not trust on error + reason = "exception: %r" % (e,) + + # SecureTransport does not send an alert nor shuts down the connection. + rec = _build_tls_unknown_ca_alert(self.version()) + self.socket.sendall(rec) + # close the connection immediately + # l_onoff = 1, activate linger + # l_linger = 0, linger for 0 seoncds + opts = struct.pack("ii", 1, 0) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, opts) + self.close() + raise ssl.SSLError("certificate verify failed, %s" % reason) + + def _evaluate_trust(self, trust_bundle): + # We want data in memory, so load it up. + if os.path.isfile(trust_bundle): + with open(trust_bundle, "rb") as f: + trust_bundle = f.read() + + cert_array = None + trust = Security.SecTrustRef() + + try: + # Get a CFArray that contains the certs we want. + cert_array = _cert_array_from_pem(trust_bundle) + + # Ok, now the hard part. We want to get the SecTrustRef that ST has + # created for this connection, shove our CAs into it, tell ST to + # ignore everything else it knows, and then ask if it can build a + # chain. This is a buuuunch of code. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + raise ssl.SSLError("Failed to copy trust reference") + + result = Security.SecTrustSetAnchorCertificates(trust, cert_array) + _assert_no_error(result) + + result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) + _assert_no_error(result) + + trust_result = Security.SecTrustResultType() + result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) + _assert_no_error(result) + finally: + if trust: + CoreFoundation.CFRelease(trust) + + if cert_array is not None: + CoreFoundation.CFRelease(cert_array) + + return trust_result.value + + def handshake( + self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase, + alpn_protocols, + ): + """ + Actually performs the TLS handshake. This is run automatically by + wrapped socket, and shouldn't be needed in user code. + """ + # First, we do the initial bits of connection setup. We need to create + # a context, set its I/O funcs, and set the connection reference. + self.context = Security.SSLCreateContext( + None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType + ) + result = Security.SSLSetIOFuncs( + self.context, _read_callback_pointer, _write_callback_pointer + ) + _assert_no_error(result) + + # Here we need to compute the handle to use. We do this by taking the + # id of self modulo 2**31 - 1. If this is already in the dictionary, we + # just keep incrementing by one until we find a free space. + with _connection_ref_lock: + handle = id(self) % 2147483647 + while handle in _connection_refs: + handle = (handle + 1) % 2147483647 + _connection_refs[handle] = self + + result = Security.SSLSetConnection(self.context, handle) + _assert_no_error(result) + + # If we have a server hostname, we should set that too. + if server_hostname: + if not isinstance(server_hostname, bytes): + server_hostname = server_hostname.encode("utf-8") + + result = Security.SSLSetPeerDomainName( + self.context, server_hostname, len(server_hostname) + ) + _assert_no_error(result) + + # Setup the ciphers. + self._set_ciphers() + + # Setup the ALPN protocols. + self._set_alpn_protocols(alpn_protocols) + + # Set the minimum and maximum TLS versions. + result = Security.SSLSetProtocolVersionMin(self.context, min_version) + _assert_no_error(result) + + result = Security.SSLSetProtocolVersionMax(self.context, max_version) + _assert_no_error(result) + + # If there's a trust DB, we need to use it. We do that by telling + # SecureTransport to break on server auth. We also do that if we don't + # want to validate the certs at all: we just won't actually do any + # authing in that case. + if not verify or trust_bundle is not None: + result = Security.SSLSetSessionOption( + self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True + ) + _assert_no_error(result) + + # If there's a client cert, we need to use it. + if client_cert: + self._keychain, self._keychain_dir = _temporary_keychain() + self._client_cert_chain = _load_client_cert_chain( + self._keychain, client_cert, client_key + ) + result = Security.SSLSetCertificate(self.context, self._client_cert_chain) + _assert_no_error(result) + + while True: + with self._raise_on_error(): + result = Security.SSLHandshake(self.context) + + if result == SecurityConst.errSSLWouldBlock: + raise socket.timeout("handshake timed out") + elif result == SecurityConst.errSSLServerAuthCompleted: + self._custom_validate(verify, trust_bundle) + continue + else: + _assert_no_error(result) + break + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, bufsiz): + buffer = ctypes.create_string_buffer(bufsiz) + bytes_read = self.recv_into(buffer, bufsiz) + data = buffer[:bytes_read] + return data + + def recv_into(self, buffer, nbytes=None): + # Read short on EOF. + if self._closed: + return 0 + + if nbytes is None: + nbytes = len(buffer) + + buffer = (ctypes.c_char * nbytes).from_buffer(buffer) + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLRead( + self.context, buffer, nbytes, ctypes.byref(processed_bytes) + ) + + # There are some result codes that we want to treat as "not always + # errors". Specifically, those are errSSLWouldBlock, + # errSSLClosedGraceful, and errSSLClosedNoNotify. + if result == SecurityConst.errSSLWouldBlock: + # If we didn't process any bytes, then this was just a time out. + # However, we can get errSSLWouldBlock in situations when we *did* + # read some data, and in those cases we should just read "short" + # and return. + if processed_bytes.value == 0: + # Timed out, no data read. + raise socket.timeout("recv timed out") + elif result in ( + SecurityConst.errSSLClosedGraceful, + SecurityConst.errSSLClosedNoNotify, + ): + # The remote peer has closed this connection. We should do so as + # well. Note that we don't actually return here because in + # principle this could actually be fired along with return data. + # It's unlikely though. + self.close() + else: + _assert_no_error(result) + + # Ok, we read and probably succeeded. We should return whatever data + # was actually read. + return processed_bytes.value + + def settimeout(self, timeout): + self._timeout = timeout + + def gettimeout(self): + return self._timeout + + def send(self, data): + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLWrite( + self.context, data, len(data), ctypes.byref(processed_bytes) + ) + + if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: + # Timed out + raise socket.timeout("send timed out") + else: + _assert_no_error(result) + + # We sent, and probably succeeded. Tell them how much we sent. + return processed_bytes.value + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + with self._raise_on_error(): + Security.SSLClose(self.context) + + def close(self): + # TODO: should I do clean shutdown here? Do I have to? + if self._makefile_refs < 1: + self._closed = True + if self.context: + CoreFoundation.CFRelease(self.context) + self.context = None + if self._client_cert_chain: + CoreFoundation.CFRelease(self._client_cert_chain) + self._client_cert_chain = None + if self._keychain: + Security.SecKeychainDelete(self._keychain) + CoreFoundation.CFRelease(self._keychain) + shutil.rmtree(self._keychain_dir) + self._keychain = self._keychain_dir = None + return self.socket.close() + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + # Urgh, annoying. + # + # Here's how we do this: + # + # 1. Call SSLCopyPeerTrust to get hold of the trust object for this + # connection. + # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. + # 3. To get the CN, call SecCertificateCopyCommonName and process that + # string so that it's of the appropriate type. + # 4. To get the SAN, we need to do something a bit more complex: + # a. Call SecCertificateCopyValues to get the data, requesting + # kSecOIDSubjectAltName. + # b. Mess about with this dictionary to try to get the SANs out. + # + # This is gross. Really gross. It's going to be a few hundred LoC extra + # just to repeat something that SecureTransport can *already do*. So my + # operating assumption at this time is that what we want to do is + # instead to just flag to urllib3 that it shouldn't do its own hostname + # validation when using SecureTransport. + if not binary_form: + raise ValueError("SecureTransport only supports dumping binary certs") + trust = Security.SecTrustRef() + certdata = None + der_bytes = None + + try: + # Grab the trust store. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + # Probably we haven't done the handshake yet. No biggie. + return None + + cert_count = Security.SecTrustGetCertificateCount(trust) + if not cert_count: + # Also a case that might happen if we haven't handshaked. + # Handshook? Handshaken? + return None + + leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) + assert leaf + + # Ok, now we want the DER bytes. + certdata = Security.SecCertificateCopyData(leaf) + assert certdata + + data_length = CoreFoundation.CFDataGetLength(certdata) + data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) + der_bytes = ctypes.string_at(data_buffer, data_length) + finally: + if certdata: + CoreFoundation.CFRelease(certdata) + if trust: + CoreFoundation.CFRelease(trust) + + return der_bytes + + def version(self): + protocol = Security.SSLProtocol() + result = Security.SSLGetNegotiatedProtocolVersion( + self.context, ctypes.byref(protocol) + ) + _assert_no_error(result) + if protocol.value == SecurityConst.kTLSProtocol13: + raise ssl.SSLError("SecureTransport does not support TLS 1.3") + elif protocol.value == SecurityConst.kTLSProtocol12: + return "TLSv1.2" + elif protocol.value == SecurityConst.kTLSProtocol11: + return "TLSv1.1" + elif protocol.value == SecurityConst.kTLSProtocol1: + return "TLSv1" + elif protocol.value == SecurityConst.kSSLProtocol3: + return "SSLv3" + elif protocol.value == SecurityConst.kSSLProtocol2: + return "SSLv2" + else: + raise ssl.SSLError("Unknown TLS version: %r" % protocol) + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + +else: # Platform-specific: Python 3 + + def makefile(self, mode="r", buffering=None, *args, **kwargs): + # We disable buffering with SecureTransport because it conflicts with + # the buffering that ST does internally (see issue #1153 for more). + buffering = 0 + return backport_makefile(self, mode, buffering, *args, **kwargs) + + +WrappedSocket.makefile = makefile + + +class SecureTransportContext(object): + """ + I am a wrapper class for the SecureTransport library, to translate the + interface of the standard library ``SSLContext`` object to calls into + SecureTransport. + """ + + def __init__(self, protocol): + self._min_version, self._max_version = _protocol_to_min_max[protocol] + self._options = 0 + self._verify = False + self._trust_bundle = None + self._client_cert = None + self._client_key = None + self._client_key_passphrase = None + self._alpn_protocols = None + + @property + def check_hostname(self): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + return True + + @check_hostname.setter + def check_hostname(self, value): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + pass + + @property + def options(self): + # TODO: Well, crap. + # + # So this is the bit of the code that is the most likely to cause us + # trouble. Essentially we need to enumerate all of the SSL options that + # users might want to use and try to see if we can sensibly translate + # them, or whether we should just ignore them. + return self._options + + @options.setter + def options(self, value): + # TODO: Update in line with above. + self._options = value + + @property + def verify_mode(self): + return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE + + @verify_mode.setter + def verify_mode(self, value): + self._verify = True if value == ssl.CERT_REQUIRED else False + + def set_default_verify_paths(self): + # So, this has to do something a bit weird. Specifically, what it does + # is nothing. + # + # This means that, if we had previously had load_verify_locations + # called, this does not undo that. We need to do that because it turns + # out that the rest of the urllib3 code will attempt to load the + # default verify paths if it hasn't been told about any paths, even if + # the context itself was sometime earlier. We resolve that by just + # ignoring it. + pass + + def load_default_certs(self): + return self.set_default_verify_paths() + + def set_ciphers(self, ciphers): + # For now, we just require the default cipher string. + if ciphers != util.ssl_.DEFAULT_CIPHERS: + raise ValueError("SecureTransport doesn't support custom cipher strings") + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + # OK, we only really support cadata and cafile. + if capath is not None: + raise ValueError("SecureTransport does not support cert directories") + + # Raise if cafile does not exist. + if cafile is not None: + with open(cafile): + pass + + self._trust_bundle = cafile or cadata + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._client_cert = certfile + self._client_key = keyfile + self._client_cert_passphrase = password + + def set_alpn_protocols(self, protocols): + """ + Sets the ALPN protocols that will later be set on the context. + + Raises a NotImplementedError if ALPN is not supported. + """ + if not hasattr(Security, "SSLSetALPNProtocols"): + raise NotImplementedError( + "SecureTransport supports ALPN only in macOS 10.12+" + ) + self._alpn_protocols = [six.ensure_binary(p) for p in protocols] + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + # So, what do we do here? Firstly, we assert some properties. This is a + # stripped down shim, so there is some functionality we don't support. + # See PEP 543 for the real deal. + assert not server_side + assert do_handshake_on_connect + assert suppress_ragged_eofs + + # Ok, we're good to go. Now we want to create the wrapped socket object + # and store it in the appropriate place. + wrapped_socket = WrappedSocket(sock) + + # Now we can handshake + wrapped_socket.handshake( + server_hostname, + self._verify, + self._trust_bundle, + self._min_version, + self._max_version, + self._client_cert, + self._client_key, + self._client_key_passphrase, + self._alpn_protocols, + ) + return wrapped_socket diff --git a/lib/urllib3/contrib/socks.py b/lib/urllib3/contrib/socks.py new file mode 100644 index 0000000..c326e80 --- /dev/null +++ b/lib/urllib3/contrib/socks.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4A (``proxy_url='socks4a://...``) +- SOCKS4 (``proxy_url='socks4://...``) +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) +- SOCKS5 with local DNS (``proxy_url='socks5://...``) +- Usernames and passwords for the SOCKS proxy + +.. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. + +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 +supports IPv4, IPv6, and domain names. + +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` +will be sent as the ``userid`` section of the SOCKS request: + +.. code-block:: python + + proxy_url="socks4a://@proxy-host" + +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion +of the ``proxy_url`` will be sent as the username/password to authenticate +with the proxy: + +.. code-block:: python + + proxy_url="socks5h://:@proxy-host" + +""" +from __future__ import absolute_import + +try: + import socks +except ImportError: + import warnings + + from ..exceptions import DependencyWarning + + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/contrib.html#socks-proxies" + ), + DependencyWarning, + ) + raise + +from socket import error as SocketError +from socket import timeout as SocketTimeout + +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + + def __init__(self, *args, **kwargs): + self._socks_options = kwargs.pop("_socks_options") + super(SOCKSConnection, self).__init__(*args, **kwargs) + + def _new_conn(self): + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], + timeout=self.timeout, + **extra_kw + ) + + except SocketTimeout: + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % error + ) + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + except SocketError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + + pool_classes_by_scheme = { + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, + } + + def __init__( + self, + proxy_url, + username=None, + password=None, + num_pools=10, + headers=None, + **connection_pool_kw + ): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(":") + if len(split) == 2: + username, password = split + if parsed.scheme == "socks5": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == "socks5h": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == "socks4": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == "socks4a": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) + + self.proxy_url = proxy_url + + socks_options = { + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, + } + connection_pool_kw["_socks_options"] = socks_options + + super(SOCKSProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw + ) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/lib/urllib3/exceptions.py b/lib/urllib3/exceptions.py new file mode 100644 index 0000000..cba6f3f --- /dev/null +++ b/lib/urllib3/exceptions.py @@ -0,0 +1,323 @@ +from __future__ import absolute_import + +from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead + +# Base Exceptions + + +class HTTPError(Exception): + """Base exception used by this module.""" + + pass + + +class HTTPWarning(Warning): + """Base warning used by this module.""" + + pass + + +class PoolError(HTTPError): + """Base exception for errors caused within a pool.""" + + def __init__(self, pool, message): + self.pool = pool + HTTPError.__init__(self, "%s: %s" % (pool, message)) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, None) + + +class RequestError(PoolError): + """Base exception for PoolErrors that have associated URLs.""" + + def __init__(self, pool, url, message): + self.url = url + PoolError.__init__(self, pool, message) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, self.url, None) + + +class SSLError(HTTPError): + """Raised when SSL certificate fails in an HTTPS connection.""" + + pass + + +class ProxyError(HTTPError): + """Raised when the connection to a proxy fails.""" + + def __init__(self, message, error, *args): + super(ProxyError, self).__init__(message, error, *args) + self.original_error = error + + +class DecodeError(HTTPError): + """Raised when automatic decoding based on Content-Type fails.""" + + pass + + +class ProtocolError(HTTPError): + """Raised when something unexpected happens mid-request/response.""" + + pass + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error + + """ + + def __init__(self, pool, url, reason=None): + self.reason = reason + + message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) + + RequestError.__init__(self, pool, url, message) + + +class HostChangedError(RequestError): + """Raised when an existing pool gets a request for a foreign host.""" + + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """Raised when passing an invalid state to a timeout""" + + pass + + +class TimeoutError(HTTPError): + """Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + ` and :exc:`ConnectTimeoutErrors `. + """ + + pass + + +class ReadTimeoutError(TimeoutError, RequestError): + """Raised when a socket timeout occurs while receiving data from a server""" + + pass + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + """Raised when a socket timeout occurs while connecting to a server""" + + pass + + +class NewConnectionError(ConnectTimeoutError, PoolError): + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + + pass + + +class EmptyPoolError(PoolError): + """Raised when a pool runs out of connections and no more are allowed.""" + + pass + + +class ClosedPoolError(PoolError): + """Raised when a request enters a pool after the pool has been closed.""" + + pass + + +class LocationValueError(ValueError, HTTPError): + """Raised when there is something wrong with a given URL input.""" + + pass + + +class LocationParseError(LocationValueError): + """Raised when get_host or similar fails to parse the URL input.""" + + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) + + self.location = location + + +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme): + message = "Not supported URL scheme %s" % scheme + super(URLSchemeUnknown, self).__init__(message) + + self.scheme = scheme + + +class ResponseError(HTTPError): + """Used as a container for an error reason supplied in a MaxRetryError.""" + + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" + + +class SecurityWarning(HTTPWarning): + """Warned when performing security reducing actions""" + + pass + + +class SubjectAltNameWarning(SecurityWarning): + """Warned when connecting to a host with a certificate missing a SAN.""" + + pass + + +class InsecureRequestWarning(SecurityWarning): + """Warned when making an unverified HTTPS request.""" + + pass + + +class SystemTimeWarning(SecurityWarning): + """Warned when system time is suspected to be wrong""" + + pass + + +class InsecurePlatformWarning(SecurityWarning): + """Warned when certain TLS/SSL configuration is not available on a platform.""" + + pass + + +class SNIMissingWarning(HTTPWarning): + """Warned when making a HTTPS request without SNI available.""" + + pass + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + + pass + + +class ResponseNotChunked(ProtocolError, ValueError): + """Response needs to be chunked in order to read it as chunks.""" + + pass + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). + """ + + pass + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of :class:`http.client.IncompleteRead` to allow int value + for ``partial`` to avoid creating large objects on streamed reads. + """ + + def __init__(self, partial, expected): + super(IncompleteRead, self).__init__(partial, expected) + + def __repr__(self): + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidChunkLength(HTTPError, httplib_IncompleteRead): + """Invalid chunk length in a chunked response.""" + + def __init__(self, response, length): + super(InvalidChunkLength, self).__init__( + response.tell(), response.length_remaining + ) + self.response = response + self.length = length + + def __repr__(self): + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) + + +class InvalidHeader(HTTPError): + """The header provided was somehow invalid.""" + + pass + + +class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): + """ProxyManager does not support the supplied scheme""" + + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme): + # 'localhost' is here because our URL parser parses + # localhost:8080 -> scheme=localhost, remove if we fix this. + if scheme == "localhost": + scheme = None + if scheme is None: + message = "Proxy URL had no scheme, should start with http:// or https://" + else: + message = ( + "Proxy URL had unsupported scheme %s, should use http:// or https://" + % scheme + ) + super(ProxySchemeUnknown, self).__init__(message) + + +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + pass + + +class HeaderParsingError(HTTPError): + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + + def __init__(self, defects, unparsed_data): + message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) + super(HeaderParsingError, self).__init__(message) + + +class UnrewindableBodyError(HTTPError): + """urllib3 encountered an error when trying to rewind a body""" + + pass diff --git a/lib/urllib3/fields.py b/lib/urllib3/fields.py new file mode 100644 index 0000000..9d630f4 --- /dev/null +++ b/lib/urllib3/fields.py @@ -0,0 +1,274 @@ +from __future__ import absolute_import + +import email.utils +import mimetypes +import re + +from .packages import six + + +def guess_content_type(filename, default="application/octet-stream"): + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param_rfc2231(name, value): + """ + Helper function to format and quote a single header parameter using the + strategy defined in RFC 2231. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows + `RFC 2388 Section 4.4 `_. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + An RFC-2231-formatted unicode string. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + if not any(ch in value for ch in '"\\\r\n'): + result = u'%s="%s"' % (name, value) + try: + result.encode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + + if six.PY2: # Python 2: + value = value.encode("utf-8") + + # encode_rfc2231 accepts an encoded string and returns an ascii-encoded + # string in Python 2 but accepts and returns unicode strings in Python 3 + value = email.utils.encode_rfc2231(value, "utf-8") + value = "%s*=%s" % (name, value) + + if six.PY2: # Python 2: + value = value.decode("utf-8") + + return value + + +_HTML5_REPLACEMENTS = { + u"\u0022": u"%22", + # Replace "\" with "\\". + u"\u005C": u"\u005C\u005C", +} + +# All control characters from 0x00 to 0x1F *except* 0x1B. +_HTML5_REPLACEMENTS.update( + { + six.unichr(cc): u"%{:02X}".format(cc) + for cc in range(0x00, 0x1F + 1) + if cc not in (0x1B,) + } +) + + +def _replace_multiple(value, needles_and_replacements): + def replacer(match): + return needles_and_replacements[match.group(0)] + + pattern = re.compile( + r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) + ) + + result = pattern.sub(replacer, value) + + return result + + +def format_header_param_html5(name, value): + """ + Helper function to format and quote a single header parameter using the + HTML5 strategy. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows the `HTML5 Working Draft + Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. + + .. _HTML5 Working Draft Section 4.10.22.7: + https://w3c.github.io/html/sec-forms.html#multipart-form-data + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + A unicode string, stripped of troublesome characters. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + value = _replace_multiple(value, _HTML5_REPLACEMENTS) + + return u'%s="%s"' % (name, value) + + +# For backwards-compatibility. +format_header_param = format_header_param_html5 + + +class RequestField(object): + """ + A data container for request body parameters. + + :param name: + The name of this request field. Must be unicode. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. Must be unicode. + :param headers: + An optional dict-like object of headers to initially use for the field. + :param header_formatter: + An optional callable that is used to encode and format the headers. By + default, this is :func:`format_header_param_html5`. + """ + + def __init__( + self, + name, + data, + filename=None, + headers=None, + header_formatter=format_header_param_html5, + ): + self._name = name + self._filename = filename + self.data = data + self.headers = {} + if headers: + self.headers = dict(headers) + self.header_formatter = header_formatter + + @classmethod + def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls( + fieldname, data, filename=filename, header_formatter=header_formatter + ) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name, value): + """ + Overridable helper function to format a single header parameter. By + default, this calls ``self.header_formatter``. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + + return self.header_formatter(name, value) + + def _render_parts(self, header_parts): + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + parts = [] + iterable = header_parts + if isinstance(header_parts, dict): + iterable = header_parts.items() + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return u"; ".join(parts) + + def render_headers(self): + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append(u"%s: %s" % (header_name, header_value)) + + lines.append(u"\r\n") + return u"\r\n".join(lines) + + def make_multipart( + self, content_disposition=None, content_type=None, content_location=None + ): + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + self.headers["Content-Disposition"] = content_disposition or u"form-data" + self.headers["Content-Disposition"] += u"; ".join( + [ + u"", + self._render_parts( + ((u"name", self._name), (u"filename", self._filename)) + ), + ] + ) + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/lib/urllib3/filepost.py b/lib/urllib3/filepost.py new file mode 100644 index 0000000..36c9252 --- /dev/null +++ b/lib/urllib3/filepost.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import + +import binascii +import codecs +import os +from io import BytesIO + +from .fields import RequestField +from .packages import six +from .packages.six import b + +writer = codecs.lookup("utf-8")[3] + + +def choose_boundary(): + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + boundary = binascii.hexlify(os.urandom(16)) + if not six.PY2: + boundary = boundary.decode("ascii") + return boundary + + +def iter_field_objects(fields): + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + if isinstance(fields, dict): + i = six.iteritems(fields) + else: + i = iter(fields) + + for field in i: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(b("--%s\r\n" % (boundary))) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, six.text_type): + writer(body).write(data) + else: + body.write(data) + + body.write(b"\r\n") + + body.write(b("--%s--\r\n" % (boundary))) + + content_type = str("multipart/form-data; boundary=%s" % boundary) + + return body.getvalue(), content_type diff --git a/lib/urllib3/packages/__init__.py b/lib/urllib3/packages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/urllib3/packages/__pycache__/__init__.cpython-39.pyc b/lib/urllib3/packages/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..430710da0f2cc47becf46ed1b024018a32c1c404 GIT binary patch literal 177 zcmYe~<>g`k0*|$lDIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o11(*(xTqIJKxa zCOEk$vmnMLwK%&ZzaS>Ppd`LHBQYhlD5fN}xWp*NCo?IgII|>Gw;(Y&J25@AIHt5H oCnqz>SRW>!A0MBYmst`YuUAlci^C>2KczG$)edCeXCP((0J3l{AOHXW literal 0 HcmV?d00001 diff --git a/lib/urllib3/packages/__pycache__/six.cpython-39.pyc b/lib/urllib3/packages/__pycache__/six.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aec8dc675b64eaad75c7fed809a70a76d64da2d2 GIT binary patch literal 27560 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 literal 0 HcmV?d00001 diff --git a/lib/urllib3/packages/backports/__pycache__/makefile.cpython-39.pyc b/lib/urllib3/packages/backports/__pycache__/makefile.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25844d41c51f55491c0f749db4c6fb96430da9aa GIT binary patch literal 1285 zcmZuwPix#p6rYi_l2-c%hlD^c$uv;Yg>(%GArL}voY>SQI3XL?VOh{hJ0q{WT1m{z zW@|IsL!CqW1$rwq^wdK8G4dJ8oC-bp-cv8>8?DzE(kJxhz2Cffe`lgrD?mVB{{9L5 zp@z`kM!8IYp-uS3Ixvd*h+=AGxQ}7AGiz%1ZH%Z#Yt(&<`wq2wsN+6Zl0NBP5Z+r+5t2|AHr2+>p=(g%f-oo#OAH6AHgU3zs@(E$=9|i58yn#HMn^ z=2r+TYL$gg-9=q_{I#l6=RN}2=l`fi4*^zJ4P`4w)!sRaroyTj2C4}YZk{{{2+(SLq)7gEW(hB*7gO!t z>sE&z+0a#uW4>JHC+lhcCdyJurUhl>GfqA)^6Z%K+hl&5#3ahHV$NuXo4Z&Y;*Rlv z&R{TT@9YB*H{!{svpk+93FC|sA>k5C1)0u-Brzj#nn(P&)6lkHQU_m0*^F&-UT|)~ z(SAEz-P4e>L})85^wp=^JCFCDy$ZMXcXqa4hP~bXwr+2oM|gSgB1-usokf&g(hqnl zL2tcc-^ktP2RtfEP#sfar==aivV>nTb<~$R!!_1E%SQz@lFLkPLVG!zXKBu~HwS-a z$Ah|-Cd-llh~~ztwn5PD(wlZ9#QhcV8xZ2(;gbi0eZjaG^hP`_2Tz%p$f6t^mNFD$ zllMR}A@2;HrSU*O-d~jPFrz~z1~Z;zX?%BM1#Dcr?2T2&y5+IHvVt%AXx@<61cux- z+{QlqP2diEhWj|EP$RkLh>_P|uWk8YYb@7bj}!3Gsa2ET1M1YY7lyPLg&}XimUfr# hyBcI=TorwZ-85GILuk@0V~@tc literal 0 HcmV?d00001 diff --git a/lib/urllib3/packages/backports/makefile.py b/lib/urllib3/packages/backports/makefile.py new file mode 100644 index 0000000..b8fb215 --- /dev/null +++ b/lib/urllib3/packages/backports/makefile.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" +backports.makefile +~~~~~~~~~~~~~~~~~~ + +Backports the Python 3 ``socket.makefile`` method for use with anything that +wants to create a "fake" socket object. +""" +import io +from socket import SocketIO + + +def backport_makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None +): + """ + Backport of ``socket.makefile`` from Python 3.5. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = SocketIO(self, rawmode) + self._makefile_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text diff --git a/lib/urllib3/packages/six.py b/lib/urllib3/packages/six.py new file mode 100644 index 0000000..f099a3d --- /dev/null +++ b/lib/urllib3/packages/six.py @@ -0,0 +1,1076 @@ +# Copyright (c) 2010-2020 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.16.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = (str,) + integer_types = (int,) + class_types = (type,) + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = (basestring,) + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + + get_source = get_code # same as get_code + + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute( + "filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse" + ), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute( + "reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload" + ), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute( + "zip_longest", "itertools", "itertools", "izip_longest", "zip_longest" + ), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule( + "collections_abc", + "collections", + "collections.abc" if sys.version_info >= (3, 3) else "collections", + ), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule( + "_dummy_thread", + "dummy_thread", + "_dummy_thread" if sys.version_info < (3, 9) else "_thread", + ), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule( + "email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart" + ), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute( + "unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes" + ), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", + "moves.urllib.parse", +) + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", + "moves.urllib.error", +) + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", + "moves.urllib.request", +) + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", + "moves.urllib.response", +) + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = ( + _urllib_robotparser_moved_attributes +) + +_importer._add_module( + Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", + "moves.urllib.robotparser", +) + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ["parse", "error", "request", "response", "robotparser"] + + +_importer._add_module( + Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib" +) + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + + def advance_iterator(it): + return it.next() + + +next = advance_iterator + + +try: + callable = callable +except NameError: + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc( + get_unbound_function, """Get the function out of a possibly unbound function""" +) + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc( + iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary." +) + + +if PY3: + + def b(s): + return s.encode("latin-1") + + def u(s): + return s + + unichr = chr + import struct + + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + + StringIO = io.StringIO + BytesIO = io.BytesIO + del io + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" +else: + + def b(s): + return s + + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape") + + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec ("""exec _code_ in _globs_, _locs_""") + + exec_( + """def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""" + ) + + +if sys.version_info[:2] > (3,): + exec_( + """def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""" + ) +else: + + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if ( + isinstance(fp, file) + and isinstance(data, unicode) + and fp.encoding is not None + ): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + + +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper( + wrapper, + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps( + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + return functools.partial( + _update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated + ) + + wraps.__doc__ = functools.wraps.__doc__ + +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + def __new__(cls, name, this_bases, d): + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d["__orig_bases__"] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + + return type.__new__(metaclass, "temporary_class", (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get("__slots__") + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop("__dict__", None) + orig_vars.pop("__weakref__", None) + if hasattr(cls, "__qualname__"): + orig_vars["__qualname__"] = cls.__qualname__ + return metaclass(cls.__name__, cls.__bases__, orig_vars) + + return wrapper + + +def ensure_binary(s, encoding="utf-8", errors="strict"): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, binary_type): + return s + if isinstance(s, text_type): + return s.encode(encoding, errors) + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding="utf-8", errors="strict"): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + # Optimization: Fast return for the common case. + if type(s) is str: + return s + if PY2 and isinstance(s, text_type): + return s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + return s + + +def ensure_text(s, encoding="utf-8", errors="strict"): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def python_2_unicode_compatible(klass): + """ + A class decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if "__str__" not in klass.__dict__: + raise ValueError( + "@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % klass.__name__ + ) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode("utf-8") + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if ( + type(importer).__name__ == "_SixMetaPathImporter" + and importer.name == __name__ + ): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/lib/urllib3/poolmanager.py b/lib/urllib3/poolmanager.py new file mode 100644 index 0000000..ca4ec34 --- /dev/null +++ b/lib/urllib3/poolmanager.py @@ -0,0 +1,537 @@ +from __future__ import absolute_import + +import collections +import functools +import logging + +from ._collections import RecentlyUsedContainer +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + ProxySchemeUnsupported, + URLSchemeUnknown, +) +from .packages import six +from .packages.six.moves.urllib.parse import urljoin +from .request import RequestMethods +from .util.proxy import connection_requires_http_tunnel +from .util.retry import Retry +from .util.url import parse_url + +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ssl_version", + "ca_cert_dir", + "ssl_context", + "key_password", + "server_hostname", +) + +# All known keyword arguments that could be provided to the pool manager, its +# pools, or the underlying connections. This is used to construct a pool key. +_key_fields = ( + "key_scheme", # str + "key_host", # str + "key_port", # int + "key_timeout", # int or float or Timeout + "key_retries", # int or Retry + "key_strict", # bool + "key_block", # bool + "key_source_address", # str + "key_key_file", # str + "key_key_password", # str + "key_cert_file", # str + "key_cert_reqs", # str + "key_ca_certs", # str + "key_ssl_version", # str + "key_ca_cert_dir", # str + "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + "key_maxsize", # int + "key_headers", # dict + "key__proxy", # parsed proxy url + "key__proxy_headers", # dict + "key__proxy_config", # class + "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples + "key__socks_options", # dict + "key_assert_hostname", # bool or string + "key_assert_fingerprint", # str + "key_server_hostname", # str +) + +#: The namedtuple class used to construct keys for the connection pool. +#: All custom key schemes should include the fields in this key at a minimum. +PoolKey = collections.namedtuple("PoolKey", _key_fields) + +_proxy_config_fields = ("ssl_context", "use_forwarding_for_https") +ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields) + + +def _default_key_normalizer(key_class, request_context): + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ("headers", "_proxy_headers", "_socks_options"): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get("socket_options") + if socket_opts is not None: + context["socket_options"] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context["key_" + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example:: + + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 + + """ + + proxy = None + proxy_config = None + + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) + self.connection_pool_kw = connection_pool_kw + self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close()) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool(self, scheme, host, port, request_context=None): + """ + Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ("scheme", "host", "port"): + request_context.pop(key, None) + + if scheme == "http": + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self): + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context["scheme"] = scheme or "http" + if not port: + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host + + return self.connection_from_context(request_context) + + def connection_from_context(self, request_context): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme.get(scheme) + if not pool_key_constructor: + raise URLSchemeUnknown(scheme) + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key(self, pool_key, request_context=None): + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url(self, url, pool_kwargs=None): + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) + + def _merge_pool_kwargs(self, override): + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def _proxy_requires_url_absolute_form(self, parsed_url): + """ + Indicates if the proxy requires the complete destination URL in the + request. Normally this is only needed when not using an HTTP CONNECT + tunnel. + """ + if self.proxy is None: + return False + + return not connection_requires_http_tunnel( + self.proxy, self.proxy_config, parsed_url.scheme + ) + + def _validate_proxy_scheme_url_selection(self, url_scheme): + """ + Validates that were not attempting to do TLS in TLS connections on + Python2 or with unsupported SSL implementations. + """ + if self.proxy is None or url_scheme != "https": + return + + if self.proxy.scheme != "https": + return + + if six.PY2 and not self.proxy_config.use_forwarding_for_https: + raise ProxySchemeUnsupported( + "Contacting HTTPS destinations through HTTPS proxies " + "'via CONNECT tunnels' is not supported in Python 2" + ) + + def urlopen(self, method, url, redirect=True, **kw): + """ + Same as :meth:`urllib3.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + self._validate_proxy_scheme_url_selection(u.scheme) + + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw["assert_same_host"] = False + kw["redirect"] = False + + if "headers" not in kw: + kw["headers"] = self.headers.copy() + + if self._proxy_requires_url_absolute_form(u): + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + # RFC 7231, Section 6.4.4 + if response.status == 303: + method = "GET" + + retries = kw.get("retries") + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + headers = list(six.iterkeys(kw["headers"])) + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + kw["headers"].pop(header, None) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + kw["retries"] = retries + kw["redirect"] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + + response.drain_conn() + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + :param proxy_ssl_context: + The proxy SSL context is used to establish the TLS connection to the + proxy when using HTTPS proxies. + + :param use_forwarding_for_https: + (Defaults to False) If set to True will forward requests to the HTTPS + proxy to be made on behalf of the client instead of creating a TLS + tunnel via the CONNECT method. **Enabling this flag means that request + and response headers and content will be visible from the HTTPS proxy** + whereas tunneling keeps request and response headers and content + private. IP address, target hostname, SNI, and port are always visible + to an HTTPS proxy even when this flag is disabled. + + Example: + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 + + """ + + def __init__( + self, + proxy_url, + num_pools=10, + headers=None, + proxy_headers=None, + proxy_ssl_context=None, + use_forwarding_for_https=False, + **connection_pool_kw + ): + + if isinstance(proxy_url, HTTPConnectionPool): + proxy_url = "%s://%s:%i" % ( + proxy_url.scheme, + proxy_url.host, + proxy_url.port, + ) + proxy = parse_url(proxy_url) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + self.proxy_ssl_context = proxy_ssl_context + self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https) + + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config + + super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw) + + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): + if scheme == "https": + return super(ProxyManager, self).connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs + ) + + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs + ) + + def _set_proxy_headers(self, url, headers=None): + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {"Accept": "*/*"} + + netloc = parse_url(url).netloc + if netloc: + headers_["Host"] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen(self, method, url, redirect=True, **kw): + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): + # For connections using HTTP CONNECT, httplib sets the necessary + # headers on the CONNECT to the proxy. If we're not using CONNECT, + # we'll definitely need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) + + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url, **kw): + return ProxyManager(proxy_url=url, **kw) diff --git a/lib/urllib3/request.py b/lib/urllib3/request.py new file mode 100644 index 0000000..398386a --- /dev/null +++ b/lib/urllib3/request.py @@ -0,0 +1,170 @@ +from __future__ import absolute_import + +from .filepost import encode_multipart_formdata +from .packages.six.moves.urllib.parse import urlencode + +__all__ = ["RequestMethods"] + + +class RequestMethods(object): + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`urllib3.HTTPConnectionPool` and + :class:`urllib3.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} + + def __init__(self, headers=None): + self.headers = headers or {} + + def urlopen( + self, + method, + url, + body=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **kw + ): # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) + + def request(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + """ + method = method.upper() + + urlopen_kw["request_url"] = url + + if method in self._encode_url_methods: + return self.request_encode_url( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + else: + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + + def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + """ + if headers is None: + headers = self.headers + + extra_kw = {"headers": headers} + extra_kw.update(urlopen_kw) + + if fields: + url += "?" + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body( + self, + method, + url, + fields=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **urlopen_kw + ): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :func:`urllib3.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :func:`urllib.parse.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + """ + if headers is None: + headers = self.headers + + extra_kw = {"headers": {}} + + if fields: + if "body" in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one." + ) + + if encode_multipart: + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) + else: + body, content_type = ( + urlencode(fields), + "application/x-www-form-urlencoded", + ) + + extra_kw["body"] = body + extra_kw["headers"] = {"Content-Type": content_type} + + extra_kw["headers"].update(headers) + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/lib/urllib3/response.py b/lib/urllib3/response.py new file mode 100644 index 0000000..0bd13d4 --- /dev/null +++ b/lib/urllib3/response.py @@ -0,0 +1,885 @@ +from __future__ import absolute_import + +import io +import logging +import sys +import warnings +import zlib +from contextlib import contextmanager +from socket import error as SocketError +from socket import timeout as SocketTimeout + +try: + try: + import brotlicffi as brotli + except ImportError: + import brotli +except ImportError: + brotli = None + +from . import util +from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPException +from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, +) +from .packages import six +from .util.response import is_fp_closed, is_response_to_head + +log = logging.getLogger(__name__) + + +class DeflateDecoder(object): + def __init__(self): + self._first_try = True + self._data = b"" + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: + return data + + if not self._first_try: + return self._obj.decompress(data) + + self._data += data + try: + decompressed = self._obj.decompress(data) + if decompressed: + self._first_try = False + self._data = None + return decompressed + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress(self._data) + finally: + self._data = None + + +class GzipDecoderState(object): + + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(object): + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA or not data: + return bytes(ret) + while True: + try: + ret += self._obj.decompress(data) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + data = self._obj.unused_data + if not data: + return bytes(ret) + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + +if brotli is not None: + + class BrotliDecoder(object): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): + self.decompress = self._obj.decompress + else: + self.decompress = self._obj.process + + def flush(self): + if hasattr(self._obj, "flush"): + return self._obj.flush() + return b"" + + +class MultiDecoder(object): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + def __init__(self, modes): + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] + + def flush(self): + return self._decoders[0].flush() + + def decompress(self, data): + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + +def _get_decoder(mode): + if "," in mode: + return MultiDecoder(mode) + + if mode == "gzip": + return GzipDecoder() + + if brotli is not None and mode == "br": + return BrotliDecoder() + + return DeflateDecoder() + + +class HTTPResponse(io.IOBase): + """ + HTTP Response container. + + Backwards-compatible with :class:`http.client.HTTPResponse` but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in :class:`http.client.HTTPResponse`: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an :class:`http.client.HTTPResponse` + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + CONTENT_DECODERS = ["gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + def __init__( + self, + body="", + headers=None, + status=0, + version=0, + reason=None, + strict=0, + preload_content=True, + decode_content=True, + original_response=None, + pool=None, + connection=None, + msg=None, + retries=None, + enforce_content_length=False, + request_method=None, + request_url=None, + auto_close=True, + ): + + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + self.retries = retries + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close + + self._decoder = None + self._body = None + self._fp = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + self._request_url = request_url + + if body and isinstance(body, (six.string_types, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, "read"): + self._fp = body + + # Are we using the chunked-style of transfer encoding? + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + + return False + + def release_conn(self): + if not self._pool or not self._connection: + return + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self): + """ + Read and discard any remaining HTTP response data in the response connection. + + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: + self.read() + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + + @property + def data(self): + # For backwards-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body + + if self._fp: + return self.read(cache_content=True) + + @property + def connection(self): + return self._connection + + def isclosed(self): + return is_fp_closed(self._fp) + + def tell(self): + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:``urllib3.response.HTTPResponse.read`` + if bytes are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method): + """ + Set initial length value for Response content if available. + """ + length = self.headers.get("content-length") + + if length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = set([int(val) for val in length.split(",")]) + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": + length = 0 + + return length + + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if len(encodings): + self._decoder = _get_decoder(content_encoding) + + DECODER_ERROR_CLASSES = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b"") + return buf + self._decoder.flush() + + return b"" + + @contextmanager + def _error_catcher(self): + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, "Read timed out.") + + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if "read operation timed out" not in str(e): + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) + + raise ReadTimeoutError(self._pool, None, "Read timed out.") + + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError("Connection broken: %r" % e, e) + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def _fp_read(self, amt): + """ + Read a response with the thought that reading the number of bytes + larger than can fit in a 32-bit int at a time via SSL in some + known cases leads to an overflow error that has to be prevented + if `amt` or `self.length_remaining` indicate that a problem may + happen. + + The known cases: + * 3.8 <= CPython < 3.9.7 because of a bug + https://github.com/urllib3/urllib3/issues/2513#issuecomment-1152559900. + * urllib3 injected with pyOpenSSL-backed SSL-support. + * CPython < 3.10 only when `amt` does not fit 32-bit int. + """ + assert self._fp + c_int_max = 2 ** 31 - 1 + if ( + ( + (amt and amt > c_int_max) + or (self.length_remaining and self.length_remaining > c_int_max) + ) + and not util.IS_SECURETRANSPORT + and (util.IS_PYOPENSSL or sys.version_info < (3, 10)) + ): + buffer = io.BytesIO() + # Besides `max_chunk_amt` being a maximum chunk size, it + # affects memory overhead of reading a response by this + # method in CPython. + # `c_int_max` equal to 2 GiB - 1 byte is the actual maximum + # chunk size that does not lead to an overflow error, but + # 256 MiB is a compromise. + max_chunk_amt = 2 ** 28 + while amt is None or amt != 0: + if amt is not None: + chunk_amt = min(amt, max_chunk_amt) + amt -= chunk_amt + else: + chunk_amt = max_chunk_amt + data = self._fp.read(chunk_amt) + if not data: + break + buffer.write(data) + del data # to reduce peak memory usage by `max_chunk_amt`. + return buffer.getvalue() + else: + # StringIO doesn't like amt=None + return self._fp.read(amt) if amt is not None else self._fp.read() + + def read(self, amt=None, decode_content=None, cache_content=False): + """ + Similar to :meth:`http.client.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if self._fp is None: + return + + flush_decoder = False + fp_closed = getattr(self._fp, "closed", False) + + with self._error_catcher(): + data = self._fp_read(amt) if not fp_closed else b"" + if amt is None: + flush_decoder = True + else: + cache_content = False + if ( + amt != 0 and not data + ): # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + if self.enforce_content_length and self.length_remaining not in ( + 0, + None, + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + + data = self._decode(data, decode_content, flush_decoder) + + if cache_content: + self._body = data + + return data + + def stream(self, amt=2 ** 16, decode_content=None): + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if self.chunked and self.supports_chunked_reads(): + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: + while not is_fp_closed(self._fp): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`http.client.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. + + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if six.PY2: + # Python 2.7 + headers = HTTPHeaderDict.from_httplib(headers) + else: + headers = HTTPHeaderDict(headers.items()) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, "strict", 0) + resp = ResponseCls( + body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw + ) + return resp + + # Backwards-compatibility methods for http.client.HTTPResponse + def getheaders(self): + warnings.warn( + "HTTPResponse.getheaders() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead access HTTPResponse.headers directly.", + category=DeprecationWarning, + stacklevel=2, + ) + return self.headers + + def getheader(self, name, default=None): + warnings.warn( + "HTTPResponse.getheader() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).", + category=DeprecationWarning, + stacklevel=2, + ) + return self.headers.get(name, default) + + # Backwards compatibility for http.cookiejar + def info(self): + return self.headers + + # Overrides from io.IOBase + def close(self): + if not self.closed: + self._fp.close() + + if self._connection: + self._connection.close() + + if not self.auto_close: + io.IOBase.close(self) + + @property + def closed(self): + if not self.auto_close: + return io.IOBase.closed.__get__(self) + elif self._fp is None: + return True + elif hasattr(self._fp, "isclosed"): + return self._fp.isclosed() + elif hasattr(self._fp, "closed"): + return self._fp.closed + else: + return True + + def fileno(self): + if self._fp is None: + raise IOError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise IOError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) + + def flush(self): + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): + return self._fp.flush() + + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + def supports_chunked_reads(self): + """ + Checks if the underlying file-like object looks like a + :class:`http.client.HTTPResponse` object. We do this by testing for + the fp attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, "fp") + + def _update_chunk_length(self): + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return + line = self._fp.fp.readline() + line = line.split(b";", 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + # Invalid chunked protocol response, abort. + self.close() + raise InvalidChunkLength(self, line) + + def _handle_chunk(self, amt): + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) + returned_chunk = chunk + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk + + def read_chunked(self, amt=None, decode_content=None): + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing." + ) + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be http.client.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: + return + + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode( + chunk, decode_content=decode_content, flush_decoder=False + ) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while True: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b"\r\n": + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + def geturl(self): + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + if self.retries is not None and len(self.retries.history): + return self.retries.history[-1].redirect_location + else: + return self._request_url + + def __iter__(self): + buffer = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunk = chunk.split(b"\n") + yield b"".join(buffer) + chunk[0] + b"\n" + for x in chunk[1:-1]: + yield x + b"\n" + if chunk[-1]: + buffer = [chunk[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/lib/urllib3/util/__init__.py b/lib/urllib3/util/__init__.py new file mode 100644 index 0000000..4547fc5 --- /dev/null +++ b/lib/urllib3/util/__init__.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import + +# For backwards compatibility, provide imports that used to be here. +from .connection import is_connection_dropped +from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers +from .response import is_fp_closed +from .retry import Retry +from .ssl_ import ( + ALPN_PROTOCOLS, + HAS_SNI, + IS_PYOPENSSL, + IS_SECURETRANSPORT, + PROTOCOL_TLS, + SSLContext, + assert_fingerprint, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import Timeout, current_time +from .url import Url, get_host, parse_url, split_first +from .wait import wait_for_read, wait_for_write + +__all__ = ( + "HAS_SNI", + "IS_PYOPENSSL", + "IS_SECURETRANSPORT", + "SSLContext", + "PROTOCOL_TLS", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "current_time", + "is_connection_dropped", + "is_fp_closed", + "get_host", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "split_first", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", +) diff --git a/lib/urllib3/util/__pycache__/__init__.cpython-39.pyc b/lib/urllib3/util/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c60f976de38c961e68ba1f0a65932550400225a GIT binary patch literal 1086 zcmZ9L%Wl&^6o&0w>Rg;_Z|MzMkdU%~BG@5>2yLWRL`f8<3r3MfZaht`&e&#V+|o5q z!-5@8Gh0@D1y-EnQi8DMPjk+Ae9r&R*ss+p7F;Law}P()%le7IzgG@|_wcA6fWQiD zZV8*%wiV=pJkN ze1$(2`($4nkOPRTb=2uNXL`WbaXTtc*Z&DdDI)88u!$r<4>b;f9QDw(}u|FADvD5(@AgWjYpHI zaUWLEDJ-nOYe#XU*%xivzEVtTdJ#qoCKDM(+O#ACiPwzIaYnMw%B()5l;ZRn8U>A} zfx(UR6RP6*71Q0Eai*bQajK0wPo-p$rWz96g2|srZb}QL>1C|6DJQ;EjHZyRs)UEw zfK>Q}+KrDtBOWlAsCWkpO0{7d2p zti~2hQR1Fv&6)zbc;Ebg(lS+VQd**pA86uRu@U3<-T;eP2h?T+2d{RTj! BAlLu^ literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/connection.cpython-39.pyc b/lib/urllib3/util/__pycache__/connection.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a34dcba097067931dc7be839325d48c7b77739ea GIT binary patch literal 3418 zcmaJ@+in}j8Qv3@D~h5HcG4Pf+G*fJXgG?K8U`w)X;oROjVQDb(lx>`SS~q3ai!%h zJu|dSV%dv0=v|9Ef^^qj^rm@%KF8kn%C8U)@QQtWq>;i?)Ouyl%@qF=ga2Gw#VaL?fAP;4;r0>QAC31B$x`qwf>~r-3ubY zx?Lg@twF@3wq?PJ455GYhHaenr_d`tB$5ZQfDKSFni5DR6S1k;WR5_<_8P52#6Uj? zZ&`*TmP};b&dP^O22ygqghj3*hLAhTfn0&$9|o`C@B7xyR_Cc;Ty&a!9*#Q?nK+io zxN|s`z8D6S@s4Cde$?3ydmRx<_Wn5N9|wm_bS6BC!rrexn8+~t;36`c<7xI*DEteG zUj%9y0>uVCMx|^_nrH9FSZnU_-M6!~^$^6MBO z-_FdrL+!aMtyjjpJgb2Ul%AQU@yv*CA=8{`JI^#(I(224g2UU!D`e-HFV4~ zYe0_qt;yAy^)u{LvY#oW*z?stuRf3S|9jBbJYMmftq6(E3e@7$}-e>-Kbd(CPFO+07 z5mME)faKmy1|)Yc(XQ3JRAIPeJB;gvD2jo=shl$@ID?aR#V9ylc>4mVd1cv-i3%Tq zpxd{JDtZVZxkn_6BB8}RX)6Cgdp#?R%MX8VemS$g{Hl>TA&rCCJGXC&+jmJ6^jJic z@285POcD|$@!{-Osz@Or!+4Ms1rsXA#m*=cvc8d35J)b?NhpU|Nk~+wY$J%G627F1MOpi6J&DA#A@ z!mC+{sjD|D>1(OJmX(V@a8=y0+M97#a|Oe5iH?dy=BNl{HaKN&VXX}iXI3!CJas}& z$1E$2c_Nc+sUO6s^1+B@mI!pQ&rGJPeg0Whg4g@F+0jc}1pg+;;uCy~4bQCL=agK_ zGF5eU%=A41-*dgpgV~% z;{fZ31mt0(iFy$5{!ljoy+jVl%BCxK>74?l4&<5eW1?*x%%{eaOxh0KL^UUB^ zRYNDOBby)0-j&v=g|UNc8ZF>y3C*HC&=(?g=4b}v4P4Rgw2@!YUl_4<4@tp0*jrXt zU#gnm((L|EwS!_RP|yhyzNmL`;gtN2+idJ{73NmmS0-Ai8P?(&9# z@Wb(o2botm?g9U$Ih8FF2iGL8#m_EXQ%W!Xd7iiI{o)70o~5Gz literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/proxy.cpython-39.pyc b/lib/urllib3/util/__pycache__/proxy.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55ff9748123742b34b92afeaf59e20ad9ca9d9c5 GIT binary patch literal 1322 zcmYjRPjA#l6d#ZOWD^o7w1QNs)KR6XtduS&pdKPbk+x|$pasEli7Z(Y&+M*=*EVm) zX@cBSdhK^;j~w_yzV^gdK!xu|@J{1h*k@J=eB|!iXuosG0uSX-GL_t_EJrbCxt@@pHArVX8bI^=ECMQXQe zqmx+)ze`YAF()>0M%WJPpP!}O?AY!;?TwYhJox4mv%K-n zJmvkx{|Fmyb{ijeV0NwF;G~>!*lohUgMXOE8tP5|P3)fcH(h8fF|{5Ra3<^Ac3mNG zIH(6`h^S-B0xKj<@q-nIS{Gz=oGGou;VE^e3x@T8k>jvRS5~I0UB8^Z8+j>J(&uth zX@()tv;kcVX!hDQOeL)axQ?e`1lLIO1!)GU?3~&wJ(s8q+Re`gX=RtY2k&azku3_a z-Tx@Mt{{-T;rFOD-ymUY+>5;EQxx&O8vG*<2LJJBz~L6^hyG+T=}6QD6r#gV(7Q`e i-JI!4cIq*35C#vHrK)rNkp5j}faI%yoo{@{&i@DX33=53 literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/queue.cpython-39.pyc b/lib/urllib3/util/__pycache__/queue.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40860b3ee5e8e69e9c261b771d3b1c0035eafc6a GIT binary patch literal 1041 zcmZ`&J8#rL5Z;IF^Ia|v1t|ClF1Vo3A%r3&5){z^iYTxwtsL(rIPojHHiGE7dH?gd{7nh@g~3of2=+kD0T@XnEl5lx zlf0lMiy1}VD|qR}9wmyM`FiHQ3gXc_B7GTL6B#J)EV$xvDCrT2Mt9JO%JC1}|^TYZguksdq8#gz-+`Qjp=4#o0_rZfM=z^S)E8ts?Ak=uq zh-oZYUZ~2Ay)x^V3&e%VFV!aaJ>b>=&=nsc;&B{qf4@dAs)3w`!0X7B$Kq-Ugc`xYSAJ8rA;Fk}9c~dtLb$dU?J5}v7=+iGe z#Dmg23T-Gvm6l2fI}xI+Wmlj-72={xi&aJA^U+A0MjG@JG}v~4hj-M-vkt(lftnCZ zNJBp5A$QqmtTAW9{$*&SH{R~U8T==+vi_#bicy8dej V8@^es@)lV%$X>{%bjku2^1qi5+H3#- literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/request.cpython-39.pyc b/lib/urllib3/util/__pycache__/request.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6be02386a29de5841206e95e9be5d121fcf6a2e6 GIT binary patch literal 3479 zcmbtX&2QVt73Yu?CDU^3c#|!feofsERSQXR+aO&SyFqNn+qg9xcb&ATV3eUaBa3T_ zWM+nO#L6hTUi8pEpogB4z4g#r{~BHk6xe^khoSG_=@Npp8JRu`P{TI$~TjS=6m4Zb`|iGhE5ex-Zkw?=HP z8?d^nSN~M5!mPV)@H%sUW_0WP=DE>bWjEO(tA9qi@319y<1?dsi!HMi_`S^*4vpsO z7a+OOB*n7dlSz~*?uElN5elXjdiU?~I7k@(!Ynu4eijRU8ph1;MSLq^qa7g<(KHLv zE0)4o@nbGLHA=b6Z$5nZ@bH(r2j1hI%||;Q!&lR~SoVW}r>d>FhVgM>4-%;^+)Q%O z_K$h2^7?TerY*+%k+1lEZnb(Ma8Sm{Q-xm}-fej0UttK1GiOZ1ePxV^BIl;K72nac z3G{o6ytc;V4LKvRt?V;0CS!-09~fg(Y>iEzEuamt&upM8KwBbWj;bhUY@LHkURPQ5 z4f&b@t2J1)#h)ju_O;aoSgnY^Fc)?%0_}it>;58$>QZpwdg{lZmdt_yu)ox4Egh*r61OuEMPcu! zch@`j*RJ@Y=gTng{7emIH*O~p9FZKor9fPiD)xsQ+$ZH}BG|-q+UX5En}{$6$D^k- zL)z(d8ka)gjxY5Z7~$2(`|2WSKJBj{2n!|Vz8Cnx;Qs_A?r1}3Dv?ARG)~(3%(bZ8 zdF2n&h;M9^w#EQ|@ZbR*`X}6*q{NSAS@0poeM_6UbpD5HTfQ3l@!cPH*4LVscKrQZ zN;EcT1365RqD31sw86C*gT~nXZHk7O{NK0VN5Li}ncP3Wh z&T=?+wlLIaUddFy{eH1@#a;P3U*f~OHZ^a)q^&%)d$U%!p$ucG{5ar+1({sfnAe4k zc~~rOZg1}#JoR?=x1T)P-T%3;P&&lh!eJbtzgaD`oFxoFna~J+i64c=?$Q~BI}6Za z<$A~#OIKp2s7~DbH$!{_?z{Na_QugO$RBxh7>F=Edc@_4O46g_RC#iM;CKW$qkeL< z7xs>1sCYZ|gA;%Nc{I)0Ooh?i37~e;QBj`{qi?|_vIB$ho=t4x5)`Y9f<(-K1~x)765~WGc0V;Ve*pGei(5C zUN!I)?1KVaqUjSrJ#iXJNR?CG0NnMJ;=@$oUIAVOT$>SP7n1Eb}d;)gJI#Q_+jmkto{fre*-<4X^grachh&=+AeDk~gXf7$~G z&O-1HGzmU15*!L5zt#~T_gVe<2rSr+!V^xD-Y0Odj0Bmb*uhCEIX}5tHX%e%oEe9M zX>@_axE&=2k{x~!2-tLLqAAaNcpC;09cIvFK;Mw`DR!Wsg>FY%Ku876 z6%C;Yfqvo#veR4;6v9T_zyTXd@m(C&aH!z$Js1l2*M5}oGO%rA*|fy>aiKQ76FOwP z9Sw9Xz{Pi9<0-rnGsRe}lhrbyVKm>?t(yLIDu6+6_1>nlub<|-h3k1`I(nW!tSKCT zRet}T#^%E1udsy%K#*G2ZhZ=a9T~pT(5+P}7U#O}VxcdRoX3!L#Z8<)*xcINnY8AI zVij)bx;>GoC=B}juzTCfVyKeL8zwA+BrTVU#a;ccL1_|aGU=3|r){ReQYWldYR)JK x@j|D5?Sqo(0|HZW9frCIe`Lk6H5`M1AoI~+b>f&c)3Nn`K4rFExQ}bA{{g&@;zR%d literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/response.cpython-39.pyc b/lib/urllib3/util/__pycache__/response.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bb064f27e0a969f95e61783adba55ee3f7c88f9 GIT binary patch literal 2326 zcmZuy&2QX96rZuZyN3N(e<&MQDOjiH4|~7FaD=&D!JLY5dih zaheUzh4jFM1OGvDG8;LN{E1`(o&Yi*Rp;?JnvpJ%>|K2Ui#9P-bzV*nlT_CRE3&-oIDGisw|QyXig^ zoTlm_&tj^0maemi1*&?dtHAwLR-_>nldVjBtwfyJ)oz?yW z$%O281HtqDI+J@U%lo5T4dht&sjrw+pY^}t!@lH-UCU{(M@LNd3lYbBc=LLpczj(j znP;hFem<$%Tn-}LOI?ix-L|)3K(1wbxQ&~ZWx4g;ZkxNf2;Or&Z@`4UOwWG-L!lBn z{m44DFvI-GihODqpcTRc@>iAy_d{b~U!Y(RxeZv!Arr`9CYP0b-A zj>*_STaq!|XJp6#C$b0vCS_E_@q_>*3_vs%HV%}a0qkdK03?A%vyd>&21z23B?RC` zpsc_-l0{}K&}9~33bJogIQ2;;0H9e)wkK)~x0~k?sEOs*#PT#AisN-?DTc00N86Gs3Cs%0XxWnGVs1S zHvo}8&FNDz$`twO#*tsOLZc{ht6Cf}HOT2C&S-cJC0b9KaCPC5S6~ zeIK5sR%pO6wy^uJ^Wv~W4HO1-_aRrKZ2qk{3ur8FgE8CPvBYcoz@;Lsu`md!qMb!? zNzb;X<){|t`cy6KO!670qf8`JRgQwGss)9iKzc*CWTwqkV>kiIR<=|^=<-`D^5g+7KYlE8p2wDF1sR zRW*t{X96(m&eExx(?wd<=a72|w0O2=pfUr|U05RZClz^3>|yBFCtz=NmMU*BhzeB* zHW&!au3noZl*fL}V?z`}naVrU0)w&M%Ive?OMc)d**=q^1t*Bhde8!IKw%=yq0MA# bDCXWuvapDmmRBU(Ftn|U8e_Kmp8NTKuX=#v literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/retry.cpython-39.pyc b/lib/urllib3/util/__pycache__/retry.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32ba69590b7758bcd5f7967bc5b9e1c40d151a03 GIT binary patch literal 16238 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 SjBHk)d>J*FcNO_>=6?Ya@oH`W literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/ssl_.cpython-39.pyc b/lib/urllib3/util/__pycache__/ssl_.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51edd785c6213cc98c01469b69ff5e36e7f6db46 GIT binary patch literal 11298 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 literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/ssl_match_hostname.cpython-39.pyc b/lib/urllib3/util/__pycache__/ssl_match_hostname.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90507d44fb1f3038b9111a4f3a3fb4c9904c7f06 GIT binary patch literal 3240 zcmZ`*&vV?i6(&G(ms+jXs>XHfOb!@#l6qBn;e^waY?10qx9Fr@wcIWDw@!A>bA&g( zNgVrfy5;`f$>E6ncJW&9{w!RR6E-o?;i(^s(n{ExLoE{*7waIdDtt*>1FS4smWr{Hq<;&BLJdk4PnT{&deFqJ+;L!8YQQjl0fgXAgpt zaja_lpZ){$TfEKZ4rfdE2sZJ~dya1IwU6k*OgVEnS3(ZQd1f;BwT&nn7*QPOlG~Qf z*~Yy)?B>;LH(nK3S&PSQ9PTqJ@6w>%<{#;1Yh>AjF+_aRA zq*l)pJup-X(-10-3SpWMPNNu&I3E^CDvY<6Cz)y8|8#AAWA)C;=Bn|lkn6S#<(Ubl z$!O+=(Tnh8C&nuxt&Cq-H6}4ZE} zRSH#FFQ88?p>lkW;PqN0AZ?1;r=<19L+`vFxNUOQUvSU60a>6Q5P1>uv336Rv27ov z3jUAK&<4yeFrVCL=te9dw+2cFphxn*Sh4j%_8GRDozs9P+BIj1~3F7 zi*p-4@TJ8C)%8s)0p`Hd864_D64h9l_A_KYFe^;5er}qhEWt$4CC1Hn_TJt9Y^d|_ zSY#cFmCad)oTVKiduYWr$)}IC%tNWR^*$QfIV76eygYP|9AqplX-RB0cFE++)fZ8$ zYfQCqpZD4Qhb-b;TI5u{I@+hHz{CrLR-EW`%y=$T_G8Vs=qH(AQO2TlH(SJO>#>OY`gR)+6f(0UP;QN-(Y&y=MR6cu#z_1vo!EYAF zCJUMzQXm`ttoOiew>?LD+Ue8rttr19Q1IJD^!wxA)qNW5ICA6$rw25nUpO`Y)d&lm zLr4LMfUUr<&$sD;Z!wkoK*zt8w3_*~Zk7#brSfabpg>O-hzQIDx@E^mnpLtyeye97 zOCkI1|B!XRBIc_m6_M?lx8KtG-P3>SR_VSZd}iN+ z%soBJTP5MMhiyHlI~V~b_vyI`|FfkNSqj-b$hJ#Tx_s^eJa;S;ptobaTrvEas0-&m z5i;qI5kQe4KBa{ixiVWk7S0fopBXI;PuTs-WR&JD|8`*w39D& zyLEUAg(U@BoPyP2k5w3d?bg*>2w#2z_|;kx{H<6d1@xsBH~A9VLEN6sj%ql|VM`o~ zLf5vPR#0cpySD|F!1O3lkhhnXC`m_B^w@gF)CdbHwNx28z1Gy`R9jUH+ALGmIWy5> zSm-fBwq^Ng&RBZqlEgA0KrE5H`7l+v$WF34D2Toi`^%_#K%0A#_kEzrm?&ni)y zX_JqpN5DRmCpc0;`_uxU!-r}@m)PWk|1bNYtjKio3v3>gCi6ljxRGH?Q+3w%w{2zp zM5J&9c+j5gDC6BTru8IBNA*okF2g{1#a0$sP5W_;%+)o|bXGJ_2w_kQ8y9g_5sN%y zXINY@ZYr{hbfvw28Fv^Jat^I-U_2X-($bf_ZpVUST2tVJC^PQ97#shYwFMocop({K zQ@QBm=DUL>)CMXi@CYRo#7F6Zr9O~hKpH3>Y2yU}lE$fT!<{E>(4rmb;Kf|B@P@u= zcpY+%+Lh!ztlA~jHKp4&%`oJ79ES1-=$4DNvTic7_lHF;b?uwkFr0E)C~sRC%SyF( utSc+;R12TlmnE{Abdh?>t@gl+W!S(8EQ5F literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/ssltransport.cpython-39.pyc b/lib/urllib3/util/__pycache__/ssltransport.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c98071b0affa4b39fc0f2d925d89156e5b393dc GIT binary patch literal 7456 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 literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/__pycache__/timeout.cpython-39.pyc b/lib/urllib3/util/__pycache__/timeout.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63f08b313bb5aa5881f643fd2c7a24b72175933f GIT binary patch literal 9127 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%0A#7o&yWWNK=|rs`eFlb5o%XWnXXEvOAz9(oj%BmE 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 Uz_Y<}&s8QXQ?BWby5kk+A7r$z9{>OV literal 0 HcmV?d00001 diff --git a/lib/urllib3/util/connection.py b/lib/urllib3/util/connection.py new file mode 100644 index 0000000..6af1138 --- /dev/null +++ b/lib/urllib3/util/connection.py @@ -0,0 +1,149 @@ +from __future__ import absolute_import + +import socket + +from ..contrib import _appengine_environ +from ..exceptions import LocationParseError +from ..packages import six +from .wait import NoWayToWaitForSocketError, wait_for_read + + +def is_connection_dropped(conn): # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + + :param conn: + :class:`http.client.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. + """ + sock = getattr(conn, "sock", False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + try: + # Returns True if readable, which here means it's been dropped + return wait_for_read(sock, timeout=0.0) + except NoWayToWaitForSocketError: # Platform-specific: AppEngine + return False + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection( + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, + socket_options=None, +): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`socket.getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith("["): + host = host.strip("[]") + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + try: + host.encode("idna") + except UnicodeError: + return six.raise_from( + LocationParseError(u"'%s', label empty or too long" % host), None + ) + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error as e: + err = e + if sock is not None: + sock.close() + sock = None + + if err is not None: + raise err + + raise socket.error("getaddrinfo returns an empty list") + + +def _set_socket_options(sock, options): + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family(): + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host): + """Returns True if the system can bind an IPv6 address.""" + sock = None + has_ipv6 = False + + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/urllib3/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6("::1") diff --git a/lib/urllib3/util/proxy.py b/lib/urllib3/util/proxy.py new file mode 100644 index 0000000..2199cc7 --- /dev/null +++ b/lib/urllib3/util/proxy.py @@ -0,0 +1,57 @@ +from .ssl_ import create_urllib3_context, resolve_cert_reqs, resolve_ssl_version + + +def connection_requires_http_tunnel( + proxy_url=None, proxy_config=None, destination_scheme=None +): + """ + Returns True if the connection requires an HTTP CONNECT through the proxy. + + :param URL proxy_url: + URL of the proxy. + :param ProxyConfig proxy_config: + Proxy configuration from poolmanager.py + :param str destination_scheme: + The scheme of the destination. (i.e https, http, etc) + """ + # If we're not using a proxy, no way to use a tunnel. + if proxy_url is None: + return False + + # HTTP destinations never require tunneling, we always forward. + if destination_scheme == "http": + return False + + # Support for forwarding with HTTPS proxies and HTTPS destinations. + if ( + proxy_url.scheme == "https" + and proxy_config + and proxy_config.use_forwarding_for_https + ): + return False + + # Otherwise always use a tunnel. + return True + + +def create_proxy_ssl_context( + ssl_version, cert_reqs, ca_certs=None, ca_cert_dir=None, ca_cert_data=None +): + """ + Generates a default proxy ssl context if one hasn't been provided by the + user. + """ + ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and hasattr(ssl_context, "load_default_certs") + ): + ssl_context.load_default_certs() + + return ssl_context diff --git a/lib/urllib3/util/queue.py b/lib/urllib3/util/queue.py new file mode 100644 index 0000000..4178410 --- /dev/null +++ b/lib/urllib3/util/queue.py @@ -0,0 +1,22 @@ +import collections + +from ..packages import six +from ..packages.six.moves import queue + +if six.PY2: + # Queue is imported for side effects on MS Windows. See issue #229. + import Queue as _unused_module_Queue # noqa: F401 + + +class LifoQueue(queue.Queue): + def _init(self, _): + self.queue = collections.deque() + + def _qsize(self, len=len): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() diff --git a/lib/urllib3/util/request.py b/lib/urllib3/util/request.py new file mode 100644 index 0000000..b574b08 --- /dev/null +++ b/lib/urllib3/util/request.py @@ -0,0 +1,146 @@ +from __future__ import absolute_import + +from base64 import b64encode + +from ..exceptions import UnrewindableBodyError +from ..packages.six import b, integer_types + +# Pass as a value within ``headers`` to skip +# emitting some HTTP headers that are added automatically. +# The only headers that are supported are ``Accept-Encoding``, +# ``Host``, and ``User-Agent``. +SKIP_HEADER = "@@@SKIP_HEADER@@@" +SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) + +ACCEPT_ENCODING = "gzip,deflate" +try: + try: + import brotlicffi as _unused_module_brotli # noqa: F401 + except ImportError: + import brotli as _unused_module_brotli # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",br" + +_FAILEDTELL = object() + + +def make_headers( + keep_alive=None, + accept_encoding=None, + user_agent=None, + basic_auth=None, + proxy_basic_auth=None, + disable_cache=None, +): + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example:: + + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} + """ + headers = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ",".join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers["accept-encoding"] = accept_encoding + + if user_agent: + headers["user-agent"] = user_agent + + if keep_alive: + headers["connection"] = "keep-alive" + + if basic_auth: + headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") + + if proxy_basic_auth: + headers["proxy-authorization"] = "Basic " + b64encode( + b(proxy_basic_auth) + ).decode("utf-8") + + if disable_cache: + headers["cache-control"] = "no-cache" + + return headers + + +def set_file_position(body, pos): + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, "tell", None) is not None: + try: + pos = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body, body_pos): + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, "seek", None) + if body_seek is not None and isinstance(body_pos, integer_types): + try: + body_seek(body_pos) + except (IOError, OSError): + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) + else: + raise ValueError( + "body_pos must be of type integer, instead it was %s." % type(body_pos) + ) diff --git a/lib/urllib3/util/response.py b/lib/urllib3/util/response.py new file mode 100644 index 0000000..5ea609c --- /dev/null +++ b/lib/urllib3/util/response.py @@ -0,0 +1,107 @@ +from __future__ import absolute_import + +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect + +from ..exceptions import HeaderParsingError +from ..packages.six.moves import http_client as httplib + + +def is_fp_closed(obj): + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers): + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param http.client.HTTPMessage headers: Headers to verify. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) + + defects = getattr(headers, "defects", None) + get_payload = getattr(headers, "get_payload", None) + + unparsed_data = None + if get_payload: + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + if defects: + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response): + """ + Checks whether the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. + + :param http.client.HTTPResponse response: + Response to check if the originating request + used 'HEAD' as a method. + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == "HEAD" diff --git a/lib/urllib3/util/retry.py b/lib/urllib3/util/retry.py new file mode 100644 index 0000000..2490d5e --- /dev/null +++ b/lib/urllib3/util/retry.py @@ -0,0 +1,620 @@ +from __future__ import absolute_import + +import email +import logging +import re +import time +import warnings +from collections import namedtuple +from itertools import takewhile + +from ..exceptions import ( + ConnectTimeoutError, + InvalidHeader, + MaxRetryError, + ProtocolError, + ProxyError, + ReadTimeoutError, + ResponseError, +) +from ..packages import six + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +RequestHistory = namedtuple( + "RequestHistory", ["method", "url", "error", "status", "redirect_location"] +) + + +# TODO: In v2 we can remove this sentinel and metaclass with deprecated options. +_Default = object() + + +class _RetryMeta(type): + @property + def DEFAULT_METHOD_WHITELIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + return cls.DEFAULT_ALLOWED_METHODS + + @DEFAULT_METHOD_WHITELIST.setter + def DEFAULT_METHOD_WHITELIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + cls.DEFAULT_ALLOWED_METHODS = value + + @property + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + return cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + @DEFAULT_REDIRECT_HEADERS_BLACKLIST.setter + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value + + @property + def BACKOFF_MAX(cls): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + return cls.DEFAULT_BACKOFF_MAX + + @BACKOFF_MAX.setter + def BACKOFF_MAX(cls, value): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + cls.DEFAULT_BACKOFF_MAX = value + + +@six.add_metaclass(_RetryMeta) +class Retry(object): + """Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool:: + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', retries=Retry(10)) + + Retries can be disabled by passing ``False``:: + + response = http.request('GET', 'http://example.com/', retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param int other: + How many times to retry on other errors. + + Other errors are errors that are not connect, read, redirect or status errors. + These errors might be raised after the request was sent to the server, so the + request might have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + If ``total`` is not set, it's a good idea to set this to 0 to account + for unexpected edge cases and avoid infinite retry loops. + + :param iterable allowed_methods: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. + + Set to a ``False`` value to retry on any verb. + + .. warning:: + + Previously this parameter was named ``method_whitelist``, that + usage is deprecated in v1.26.0 and will be removed in v2.0. + + :param iterable status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``allowed_methods`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of total retries} - 1)) + + seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep + for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer + than :attr:`Retry.DEFAULT_BACKOFF_MAX`. + + By default, backoff is disabled (set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param iterable remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + """ + + #: Default methods to be used for ``allowed_methods`` + DEFAULT_ALLOWED_METHODS = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + + #: Default status codes to be used for ``status_forcelist`` + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + #: Default headers to be used for ``remove_headers_on_redirect`` + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"]) + + #: Maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 + + def __init__( + self, + total=10, + connect=None, + read=None, + redirect=None, + status=None, + other=None, + allowed_methods=_Default, + status_forcelist=None, + backoff_factor=0, + raise_on_redirect=True, + raise_on_status=True, + history=None, + respect_retry_after_header=True, + remove_headers_on_redirect=_Default, + # TODO: Deprecated, remove in v2.0 + method_whitelist=_Default, + ): + + if method_whitelist is not _Default: + if allowed_methods is not _Default: + raise ValueError( + "Using both 'allowed_methods' and " + "'method_whitelist' together is not allowed. " + "Instead only use 'allowed_methods'" + ) + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + stacklevel=2, + ) + allowed_methods = method_whitelist + if allowed_methods is _Default: + allowed_methods = self.DEFAULT_ALLOWED_METHODS + if remove_headers_on_redirect is _Default: + remove_headers_on_redirect = self.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + self.total = total + self.connect = connect + self.read = read + self.status = status + self.other = other + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.allowed_methods = allowed_methods + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or tuple() + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = frozenset( + [h.lower() for h in remove_headers_on_redirect] + ) + + def new(self, **kw): + params = dict( + total=self.total, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, + ) + + # TODO: If already given in **kw we use what's given to us + # If not given we need to figure out what to pass. We decide + # based on whether our class has the 'method_whitelist' property + # and if so we pass the deprecated 'method_whitelist' otherwise + # we use 'allowed_methods'. Remove in v2.0 + if "method_whitelist" not in kw and "allowed_methods" not in kw: + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + params["method_whitelist"] = self.allowed_methods + else: + params["allowed_methods"] = self.allowed_methods + + params.update(kw) + return type(self)(**params) + + @classmethod + def from_int(cls, retries, redirect=True, default=None): + """Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self): + """Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + return min(self.DEFAULT_BACKOFF_MAX, backoff_value) + + def parse_retry_after(self, retry_after): + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + def get_retry_after(self, response): + """Get the value of Retry-After in seconds.""" + + retry_after = response.headers.get("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response=None): + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self): + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response=None): + """Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if self.respect_retry_after_header and response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err): + """Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + if isinstance(err, ProxyError): + err = err.original_error + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err): + """Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method): + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods + """ + # TODO: For now favor if the Retry implementation sets its own method_whitelist + # property outside of our constructor to avoid breaking custom implementations. + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + allowed_methods = self.method_whitelist + else: + allowed_methods = self.allowed_methods + + if allowed_methods and method.upper() not in allowed_methods: + return False + return True + + def is_retry(self, method, status_code, has_retry_after=False): + """Is this method/status code retryable? (Based on allowlists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return ( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) + + def is_exhausted(self): + """Are we out of retries?""" + retry_counts = ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment( + self, + method=None, + url=None, + response=None, + error=None, + _pool=None, + _stacktrace=None, + ): + """Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.HTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise six.reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + other = self.other + cause = "unknown" + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise six.reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or not self._is_method_retryable(method): + raise six.reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif error: + # Other retry? + if other is not None: + other -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = "too many redirects" + redirect_location = response.get_redirect_location() + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and the given method is in the allowed_methods + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) + status = response.status + + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) + + new_retry = self.new( + total=total, + connect=connect, + read=read, + redirect=redirect, + status=status_count, + other=other, + history=history, + ) + + if new_retry.is_exhausted(): + raise MaxRetryError(_pool, url, error or ResponseError(cause)) + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self): + return ( + "{cls.__name__}(total={self.total}, connect={self.connect}, " + "read={self.read}, redirect={self.redirect}, status={self.status})" + ).format(cls=type(self), self=self) + + def __getattr__(self, item): + if item == "method_whitelist": + # TODO: Remove this deprecated alias in v2.0 + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + return self.allowed_methods + try: + return getattr(super(Retry, self), item) + except AttributeError: + return getattr(Retry, item) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/lib/urllib3/util/ssl_.py b/lib/urllib3/util/ssl_.py new file mode 100644 index 0000000..8f86781 --- /dev/null +++ b/lib/urllib3/util/ssl_.py @@ -0,0 +1,495 @@ +from __future__ import absolute_import + +import hmac +import os +import sys +import warnings +from binascii import hexlify, unhexlify +from hashlib import md5, sha1, sha256 + +from ..exceptions import ( + InsecurePlatformWarning, + ProxySchemeUnsupported, + SNIMissingWarning, + SSLError, +) +from ..packages import six +from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE + +SSLContext = None +SSLTransport = None +HAS_SNI = False +IS_PYOPENSSL = False +IS_SECURETRANSPORT = False +ALPN_PROTOCOLS = ["http/1.1"] + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256} + + +def _const_compare_digest_backport(a, b): + """ + Compare two digests of equal length in constant time. + + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for left, right in zip(bytearray(a), bytearray(b)): + result |= left ^ right + return result == 0 + + +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) + +try: # Test for SSL features + import ssl + from ssl import CERT_REQUIRED, wrap_socket +except ImportError: + pass + +try: + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + +try: + from .ssltransport import SSLTransport +except ImportError: + pass + + +try: # Platform-specific: Python 3.6 + from ssl import PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS +except ImportError: + try: + from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS + except ImportError: + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 + +try: + from ssl import PROTOCOL_TLS_CLIENT +except ImportError: + PROTOCOL_TLS_CLIENT = PROTOCOL_TLS + + +try: + from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3 +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 + + +try: # OP_NO_TICKET was added in Python 3.6 + from ssl import OP_NO_TICKET +except ImportError: + OP_NO_TICKET = 0x4000 + + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and +# security, +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, +# - disable NULL authentication, MD5 MACs, DSS, and other +# insecure ciphers for security reasons. +# - NOTE: TLS 1.3 cipher suites are managed through a different interface +# not exposed by CPython (yet!) and are enabled by default if they're available. +DEFAULT_CIPHERS = ":".join( + [ + "ECDHE+AESGCM", + "ECDHE+CHACHA20", + "DHE+AESGCM", + "DHE+CHACHA20", + "ECDH+AESGCM", + "DH+AESGCM", + "ECDH+AES", + "DH+AES", + "RSA+AESGCM", + "RSA+AES", + "!aNULL", + "!eNULL", + "!MD5", + "!DSS", + ] +) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + + class SSLContext(object): # Platform-specific: Python 2 + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + if cadata is not None: + raise SSLError("CA data not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None, server_side=False): + warnings.warn( + "A true SSLContext object is not available. This prevents " + "urllib3 from configuring SSL appropriately and may cause " + "certain SSL connections to fail. You can upgrade to a newer " + "version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + InsecurePlatformWarning, + ) + kwargs = { + "keyfile": self.keyfile, + "certfile": self.certfile, + "ca_certs": self.ca_certs, + "cert_reqs": self.verify_mode, + "ssl_version": self.protocol, + "server_side": server_side, + } + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + + +def assert_fingerprint(cert, fingerprint): + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + fingerprint = fingerprint.replace(":", "").lower() + digest_length = len(fingerprint) + hashfunc = HASHFUNC_MAP.get(digest_length) + if not hashfunc: + raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not _const_compare_digest(cert_digest, fingerprint_bytes): + raise SSLError( + 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( + fingerprint, hexlify(cert_digest) + ) + ) + + +def resolve_cert_reqs(candidate): + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_REQUIRED`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_REQUIRED + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "CERT_" + candidate) + return res + + return candidate + + +def resolve_ssl_version(candidate): + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_TLS + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "PROTOCOL_" + candidate) + return res + + return candidate + + +def create_urllib3_context( + ssl_version=None, cert_reqs=None, options=None, ciphers=None +): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. + :param ciphers: + Which cipher suites to allow the server to select. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + # PROTOCOL_TLS is deprecated in Python 3.10 + if not ssl_version or ssl_version == PROTOCOL_TLS: + ssl_version = PROTOCOL_TLS_CLIENT + + context = SSLContext(ssl_version) + + context.set_ciphers(ciphers or DEFAULT_CIPHERS) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + # TLSv1.2 only. Unless set explicitly, do not request tickets. + # This may save some bandwidth on wire, and although the ticket is encrypted, + # there is a risk associated with it being on wire, + # if the server is not rotating its ticketing keys properly. + options |= OP_NO_TICKET + + context.options |= options + + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older + # versions of Python. We only enable on Python 3.7.4+ or if certificate + # verification is enabled to work around Python issue #37428 + # See: https://bugs.python.org/issue37428 + if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( + context, "post_handshake_auth", None + ) is not None: + context.post_handshake_auth = True + + def disable_check_hostname(): + if ( + getattr(context, "check_hostname", None) is not None + ): # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + + # The order of the below lines setting verify_mode and check_hostname + # matter due to safe-guards SSLContext has to prevent an SSLContext with + # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more + # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used + # or not so we don't know the initial state of the freshly created SSLContext. + if cert_reqs == ssl.CERT_REQUIRED: + context.verify_mode = cert_reqs + disable_check_hostname() + else: + disable_check_hostname() + context.verify_mode = cert_reqs + + # Enable logging of TLS session keys via defacto standard environment variable + # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values. + if hasattr(context, "keylog_filename"): + sslkeylogfile = os.environ.get("SSLKEYLOGFILE") + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + + return context + + +def ssl_wrap_socket( + sock, + keyfile=None, + certfile=None, + cert_reqs=None, + ca_certs=None, + server_hostname=None, + ssl_version=None, + ciphers=None, + ssl_context=None, + ca_cert_dir=None, + key_password=None, + ca_cert_data=None, + tls_in_tls=False, +): + """ + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + :param key_password: + Optional password if the keyfile is encrypted. + :param ca_cert_data: + Optional string containing CA certificates in PEM format suitable for + passing as the cadata parameter to SSLContext.load_verify_locations() + :param tls_in_tls: + Use SSLTransport to wrap the existing socket. + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are no longer + # used by urllib3 itself. We should consider deprecating and removing + # this code. + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) + + if ca_certs or ca_cert_dir or ca_cert_data: + try: + context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) + except (IOError, OSError) as e: + raise SSLError(e) + + elif ssl_context is None and hasattr(context, "load_default_certs"): + # try to load OS default certs; works well on Windows (require Python3.4+) + context.load_default_certs() + + # Attempt to detect if we get the goofy behavior of the + # keyfile being encrypted and OpenSSL asking for the + # passphrase via the terminal and instead error out. + if keyfile and key_password is None and _is_key_file_encrypted(keyfile): + raise SSLError("Client private key is encrypted, password is required") + + if certfile: + if key_password is None: + context.load_cert_chain(certfile, keyfile) + else: + context.load_cert_chain(certfile, keyfile, key_password) + + try: + if hasattr(context, "set_alpn_protocols"): + context.set_alpn_protocols(ALPN_PROTOCOLS) + except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols + pass + + # If we detect server_hostname is an IP address then the SNI + # extension should not be used according to RFC3546 Section 3.1 + use_sni_hostname = server_hostname and not is_ipaddress(server_hostname) + # SecureTransport uses server_hostname in certificate verification. + send_sni = (use_sni_hostname and HAS_SNI) or ( + IS_SECURETRANSPORT and server_hostname + ) + # Do not warn the user if server_hostname is an invalid SNI hostname. + if not HAS_SNI and use_sni_hostname: + warnings.warn( + "An HTTPS request has been made, but the SNI (Server Name " + "Indication) extension to TLS is not available on this platform. " + "This may cause the server to present an incorrect TLS " + "certificate, which can cause validation failures. You can upgrade to " + "a newer version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + SNIMissingWarning, + ) + + if send_sni: + ssl_sock = _ssl_wrap_socket_impl( + sock, context, tls_in_tls, server_hostname=server_hostname + ) + else: + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls) + return ssl_sock + + +def is_ipaddress(hostname): + """Detects whether the hostname given is an IPv4 or IPv6 address. + Also detects IPv6 addresses with Zone IDs. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if not six.PY2 and isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode("ascii") + return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + + +def _is_key_file_encrypted(key_file): + """Detects if a key file is encrypted or not.""" + with open(key_file, "r") as f: + for line in f: + # Look for Proc-Type: 4,ENCRYPTED + if "ENCRYPTED" in line: + return True + + return False + + +def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None): + if tls_in_tls: + if not SSLTransport: + # Import error, ssl is not available. + raise ProxySchemeUnsupported( + "TLS in TLS requires support for the 'ssl' module" + ) + + SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) + return SSLTransport(sock, ssl_context, server_hostname) + + if server_hostname: + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + else: + return ssl_context.wrap_socket(sock) diff --git a/lib/urllib3/util/ssl_match_hostname.py b/lib/urllib3/util/ssl_match_hostname.py new file mode 100644 index 0000000..1dd950c --- /dev/null +++ b/lib/urllib3/util/ssl_match_hostname.py @@ -0,0 +1,159 @@ +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html + +import re +import sys + +# ipaddress has been backported to 2.6+ in pypi. If it is installed on the +# system, use it to handle IPAddress ServerAltnames (this was added in +# python-3.5) otherwise only do DNS matching. This allows +# util.ssl_match_hostname to continue to be used in Python 2.7. +try: + import ipaddress +except ImportError: + ipaddress = None + +__version__ = "3.5.0.1" + + +class CertificateError(ValueError): + pass + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r".") + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count("*") + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn) + ) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == "*": + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) + return pat.match(hostname) + + +def _to_unicode(obj): + if isinstance(obj, str) and sys.version_info < (3,): + # ignored flake8 # F821 to support python 2.7 function + obj = unicode(obj, encoding="ascii", errors="strict") # noqa: F821 + return obj + + +def _ipaddress_match(ipname, host_ip): + """Exact matching of IP addresses. + + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) + return ip == host_ip + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) + try: + # Divergence from upstream: ipaddress can't handle byte str + host_ip = ipaddress.ip_address(_to_unicode(hostname)) + except (UnicodeError, ValueError): + # ValueError: Not an IP address (common case) + # UnicodeError: Divergence from upstream: Have to deal with ipaddress not taking + # byte strings. addresses should be all ascii, so we consider it not + # an ipaddress in this case + host_ip = None + except AttributeError: + # Divergence from upstream: Make ipaddress library optional + if ipaddress is None: + host_ip = None + else: # Defensive + raise + dnsnames = [] + san = cert.get("subjectAltName", ()) + for key, value in san: + if key == "DNS": + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == "IP Address": + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get("subject", ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == "commonName": + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) + else: + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/lib/urllib3/util/ssltransport.py b/lib/urllib3/util/ssltransport.py new file mode 100644 index 0000000..4a7105d --- /dev/null +++ b/lib/urllib3/util/ssltransport.py @@ -0,0 +1,221 @@ +import io +import socket +import ssl + +from ..exceptions import ProxySchemeUnsupported +from ..packages import six + +SSL_BLOCKSIZE = 16384 + + +class SSLTransport: + """ + The SSLTransport wraps an existing socket and establishes an SSL connection. + + Contrary to Python's implementation of SSLSocket, it allows you to chain + multiple TLS connections together. It's particularly useful if you need to + implement TLS within TLS. + + The class supports most of the socket API operations. + """ + + @staticmethod + def _validate_ssl_context_for_tls_in_tls(ssl_context): + """ + Raises a ProxySchemeUnsupported if the provided ssl_context can't be used + for TLS in TLS. + + The only requirement is that the ssl_context provides the 'wrap_bio' + methods. + """ + + if not hasattr(ssl_context, "wrap_bio"): + if six.PY2: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "supported on Python 2" + ) + else: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True + ): + """ + Create an SSLTransport around socket using the provided ssl_context. + """ + self.incoming = ssl.MemoryBIO() + self.outgoing = ssl.MemoryBIO() + + self.suppress_ragged_eofs = suppress_ragged_eofs + self.socket = socket + + self.sslobj = ssl_context.wrap_bio( + self.incoming, self.outgoing, server_hostname=server_hostname + ) + + # Perform initial handshake. + self._ssl_io_loop(self.sslobj.do_handshake) + + def __enter__(self): + return self + + def __exit__(self, *_): + self.close() + + def fileno(self): + return self.socket.fileno() + + def read(self, len=1024, buffer=None): + return self._wrap_ssl_read(len, buffer) + + def recv(self, len=1024, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(len) + + def recv_into(self, buffer, nbytes=None, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if buffer and (nbytes is None): + nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 + return self.read(nbytes, buffer) + + def sendall(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to sendall") + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + v = self.send(byte_view[count:]) + count += v + + def send(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + response = self._ssl_io_loop(self.sslobj.write, data) + return response + + def makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None + ): + """ + Python's httpclient uses makefile and buffered io when reading HTTP + messages and we need to support it. + + This is unfortunately a copy and paste of socket.py makefile with small + changes to point to the socket directly. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = socket.SocketIO(self, rawmode) + self.socket._io_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text + + def unwrap(self): + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self): + self.socket.close() + + def getpeercert(self, binary_form=False): + return self.sslobj.getpeercert(binary_form) + + def version(self): + return self.sslobj.version() + + def cipher(self): + return self.sslobj.cipher() + + def selected_alpn_protocol(self): + return self.sslobj.selected_alpn_protocol() + + def selected_npn_protocol(self): + return self.sslobj.selected_npn_protocol() + + def shared_ciphers(self): + return self.sslobj.shared_ciphers() + + def compression(self): + return self.sslobj.compression() + + def settimeout(self, value): + self.socket.settimeout(value) + + def gettimeout(self): + return self.socket.gettimeout() + + def _decref_socketios(self): + self.socket._decref_socketios() + + def _wrap_ssl_read(self, len, buffer=None): + try: + return self._ssl_io_loop(self.sslobj.read, len, buffer) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: + return 0 # eof, return 0. + else: + raise + + def _ssl_io_loop(self, func, *args): + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + ret = func(*args) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + # WANT_READ, and WANT_WRITE are expected, others are not. + raise e + errno = e.errno + + buf = self.outgoing.read() + self.socket.sendall(buf) + + if errno is None: + should_loop = False + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = self.socket.recv(SSL_BLOCKSIZE) + if buf: + self.incoming.write(buf) + else: + self.incoming.write_eof() + return ret diff --git a/lib/urllib3/util/timeout.py b/lib/urllib3/util/timeout.py new file mode 100644 index 0000000..78e18a6 --- /dev/null +++ b/lib/urllib3/util/timeout.py @@ -0,0 +1,271 @@ +from __future__ import absolute_import + +import time + +# The default socket timeout, used by httplib to indicate that no timeout was; specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT, getdefaulttimeout + +from ..exceptions import TimeoutStateError + +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() + + +# Use time.monotonic if available. +current_time = getattr(time, "monotonic", time.time) + + +class Timeout(object): + """Timeout configuration. + + Timeouts can be defined as a default for a pool: + + .. code-block:: python + + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``: + + .. code-block:: python + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: int, float, or None + + :param connect: + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py + `_. + None will set an infinite timeout for connection attempts. + + :type connect: int, float, or None + + :param read: + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py + `_. + None will set an infinite timeout. + + :type read: int, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") + self._start_connect = None + + def __repr__(self): + return "%s(connect=%r, read=%r, total=%r)" % ( + type(self).__name__, + self._connect, + self._read, + self.total, + ) + + # __str__ provided for backwards compatibility + __str__ = __repr__ + + @classmethod + def resolve_default_timeout(cls, timeout): + return getdefaulttimeout() if timeout is cls.DEFAULT_TIMEOUT else timeout + + @classmethod + def _validate_timeout(cls, value, name): + """Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) + try: + float(value) + except (TypeError, ValueError): + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) + + try: + if value <= 0: + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) + except TypeError: + # Python 3 + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) + + return value + + @classmethod + def from_float(cls, timeout): + """Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, sentinel default object, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self): + """Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, total=self.total) + + def start_connect(self): + """Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = current_time() + return self._start_connect + + def get_connect_duration(self): + """Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time in seconds. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) + return current_time() - self._start_connect + + @property + def connect_timeout(self): + """Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) + + @property + def read_timeout(self): + """Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if ( + self.total is not None + and self.total is not self.DEFAULT_TIMEOUT + and self._read is not None + and self._read is not self.DEFAULT_TIMEOUT + ): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), self._read)) + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self._read diff --git a/lib/urllib3/util/url.py b/lib/urllib3/util/url.py new file mode 100644 index 0000000..e5682d3 --- /dev/null +++ b/lib/urllib3/util/url.py @@ -0,0 +1,435 @@ +from __future__ import absolute_import + +import re +from collections import namedtuple + +from ..exceptions import LocationParseError +from ..packages import six + +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"] + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +NORMALIZABLE_SCHEMES = ("http", "https", None) + +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^\\/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) + +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +HEX_PAT = "[0-9A-Fa-f]{1,4}" +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT) +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~" +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]" +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +IPV4_RE = re.compile("^" + IPV4_PAT + "$") +IPV6_RE = re.compile("^" + IPV6_PAT + "$") +IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$") +BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$") +ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$") + +_HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % ( + REG_NAME_PAT, + IPV4_PAT, + IPV6_ADDRZ_PAT, +) +_HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) + +UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +SUB_DELIM_CHARS = set("!$&'()*+,;=") +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"} +PATH_CHARS = USERINFO_CHARS | {"@", "/"} +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"} + + +class Url(namedtuple("Url", url_attrs)): + """ + Data structure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + + __slots__ = () + + def __new__( + cls, + scheme=None, + auth=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: + scheme = scheme.lower() + return super(Url, cls).__new__( + cls, scheme, auth, host, port, path, query, fragment + ) + + @property + def hostname(self): + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self): + """Absolute path including the query string.""" + uri = self.path or "/" + + if self.query is not None: + uri += "?" + self.query + + return uri + + @property + def netloc(self): + """Network location including host and port""" + if self.port: + return "%s:%d" % (self.host, self.port) + return self.host + + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = u"" + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + u"://" + if auth is not None: + url += auth + u"@" + if host is not None: + url += host + if port is not None: + url += u":" + str(port) + if path is not None: + url += path + if query is not None: + url += u"?" + query + if fragment is not None: + url += u"#" + fragment + + return url + + def __str__(self): + return self.url + + +def split_first(s, delims): + """ + .. deprecated:: 1.25 + + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, "", None + + return s[:min_idx], s[min_idx + 1 :], min_delim + + +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"): + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = six.ensure_text(component) + + # Normalize existing percent-encoded bytes. + # Try to see if the component we're encoding is already percent-encoded + # so we can skip all '%' characters but still encode all others. + component, percent_encodings = PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") + encoded_component = bytearray() + + for i in range(0, len(uri_bytes)): + # Will return a single character bytestring on both Python 2 & 3 + byte = uri_bytes[i : i + 1] + byte_ord = ord(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte + continue + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) + + return encoded_component.decode(encoding) + + +def _remove_path_dot_segments(path): + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + elif segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +def _normalize_host(host, scheme): + if host: + if isinstance(host, six.binary_type): + host = six.ensure_str(host) + + if scheme in NORMALIZABLE_SCHEMES: + is_ipv6 = IPV6_ADDRZ_RE.match(host) + if is_ipv6: + # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as + # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID + # separator as necessary to return a valid RFC 4007 scoped IP. + match = ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS) + return host[:start].lower() + zone_id + host[end:] + else: + return host.lower() + elif not IPV4_RE.match(host): + return six.ensure_str( + b".".join([_idna_encode(label) for label in host.split(".")]) + ) + return host + + +def _idna_encode(name): + if name and any(ord(x) >= 128 for x in name): + try: + import idna + except ImportError: + six.raise_from( + LocationParseError("Unable to parse URL without the 'idna' module"), + None, + ) + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + six.raise_from( + LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None + ) + return name.lower().encode("ascii") + + +def _encode_target(target): + """Percent-encodes a request target so that there are no invalid characters""" + path, query = TARGET_RE.match(target).groups() + target = _encode_invalid_chars(path, PATH_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) + if query is not None: + target += "?" + query + return target + + +def parse_url(url): + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + This parser is RFC 3986 and RFC 6874 compliant. + + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + + :param str url: URL to parse into a :class:`.Url` namedtuple. + + Partly backwards-compatible with :mod:`urlparse`. + + Example:: + + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + if not url: + # Empty + return Url() + + source_url = url + if not SCHEME_RE.search(url): + url = "//" + url + + try: + scheme, authority, path, query, fragment = URI_RE.match(url).groups() + normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES + + if scheme: + scheme = scheme.lower() + + if authority: + auth, _, host_port = authority.rpartition("@") + auth = auth or None + host, port = _HOST_PORT_RE.match(host_port).groups() + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, USERINFO_CHARS) + if port == "": + port = None + else: + auth, host, port = None, None, None + + if port is not None: + port = int(port) + if not (0 <= port <= 65535): + raise LocationParseError(url) + + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS) + + except (ValueError, AttributeError): + return six.raise_from(LocationParseError(source_url), None) + + # For the sake of backwards compatibility we put empty + # string values for path if there are any defined values + # beyond the path in the URL. + # TODO: Remove this when we break backwards compatibility. + if not path: + if query is not None or fragment is not None: + path = "" + else: + path = None + + # Ensure that each part of the URL is a `str` for + # backwards compatibility. + if isinstance(url, six.text_type): + ensure_func = six.ensure_text + else: + ensure_func = six.ensure_str + + def ensure_type(x): + return x if x is None else ensure_func(x) + + return Url( + scheme=ensure_type(scheme), + auth=ensure_type(auth), + host=ensure_type(host), + port=port, + path=ensure_type(path), + query=ensure_type(query), + fragment=ensure_type(fragment), + ) + + +def get_host(url): + """ + Deprecated. Use :func:`parse_url` instead. + """ + p = parse_url(url) + return p.scheme or "http", p.hostname, p.port diff --git a/lib/urllib3/util/wait.py b/lib/urllib3/util/wait.py new file mode 100644 index 0000000..21b4590 --- /dev/null +++ b/lib/urllib3/util/wait.py @@ -0,0 +1,152 @@ +import errno +import select +import sys +from functools import partial + +try: + from time import monotonic +except ImportError: + from time import time as monotonic + +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] + + +class NoWayToWaitForSocketError(Exception): + pass + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + +if sys.version_info >= (3, 5): + # Modern Python, that retries syscalls by default + def _retry_on_intr(fn, timeout): + return fn(timeout) + +else: + # Old and broken Pythons. + def _retry_on_intr(fn, timeout): + if timeout is None: + deadline = float("inf") + else: + deadline = monotonic() + timeout + + while True: + try: + return fn(timeout) + # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 + except (OSError, select.error) as e: + # 'e.args[0]' incantation works for both OSError and select.error + if e.args[0] != errno.EINTR: + raise + else: + timeout = deadline - monotonic() + if timeout < 0: + timeout = 0 + if timeout == float("inf"): + timeout = None + continue + + +def select_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = _retry_on_intr(fn, timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t): + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(_retry_on_intr(do_poll, timeout)) + + +def null_wait_for_socket(*args, **kwargs): + raise NoWayToWaitForSocketError("no select-equivalent available") + + +def _have_working_poll(): + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + _retry_on_intr(poll_obj.poll, 0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket(*args, **kwargs): + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + else: # Platform-specific: Appengine. + wait_for_socket = null_wait_for_socket + return wait_for_socket(*args, **kwargs) + + +def wait_for_read(sock, timeout=None): + """Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock, timeout=None): + """Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/lib/yarl-1.8.2.dist-info/INSTALLER b/lib/yarl-1.8.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lib/yarl-1.8.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lib/yarl-1.8.2.dist-info/LICENSE b/lib/yarl-1.8.2.dist-info/LICENSE new file mode 100644 index 0000000..fa53b2b --- /dev/null +++ b/lib/yarl-1.8.2.dist-info/LICENSE @@ -0,0 +1,13 @@ + Copyright 2016-2021, Andrew Svetlov and aio-libs team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/yarl-1.8.2.dist-info/METADATA b/lib/yarl-1.8.2.dist-info/METADATA new file mode 100644 index 0000000..68acc87 --- /dev/null +++ b/lib/yarl-1.8.2.dist-info/METADATA @@ -0,0 +1,873 @@ +Metadata-Version: 2.1 +Name: yarl +Version: 1.8.2 +Summary: Yet another URL library +Home-page: https://github.com/aio-libs/yarl/ +Author: Andrew Svetlov +Author-email: andrew.svetlov@gmail.com +License: Apache 2 +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: multidict (>=4.0) +Requires-Dist: idna (>=2.0) +Requires-Dist: typing-extensions (>=3.7.4) ; python_version < "3.8" + +yarl +==== + +.. image:: https://github.com/aio-libs/yarl/workflows/CI/badge.svg + :target: https://github.com/aio-libs/yarl/actions?query=workflow%3ACI + :align: right + +.. image:: https://codecov.io/gh/aio-libs/yarl/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/yarl + +.. image:: https://badge.fury.io/py/yarl.svg + :target: https://badge.fury.io/py/yarl + + +.. image:: https://readthedocs.org/projects/yarl/badge/?version=latest + :target: https://yarl.readthedocs.io + + +.. image:: https://img.shields.io/pypi/pyversions/yarl.svg + :target: https://pypi.python.org/pypi/yarl + +.. image:: https://badges.gitter.im/Join%20Chat.svg + :target: https://gitter.im/aio-libs/Lobby + :alt: Chat on Gitter + +Introduction +------------ + +Url is constructed from ``str``: + +.. code-block:: pycon + + >>> from yarl import URL + >>> url = URL('https://www.python.org/~guido?arg=1#frag') + >>> url + URL('https://www.python.org/~guido?arg=1#frag') + +All url parts: *scheme*, *user*, *password*, *host*, *port*, *path*, +*query* and *fragment* are accessible by properties: + +.. code-block:: pycon + + >>> url.scheme + 'https' + >>> url.host + 'www.python.org' + >>> url.path + '/~guido' + >>> url.query_string + 'arg=1' + >>> url.query + + >>> url.fragment + 'frag' + +All url manipulations produce a new url object: + +.. code-block:: pycon + + >>> url = URL('https://www.python.org') + >>> url / 'foo' / 'bar' + URL('https://www.python.org/foo/bar') + >>> url / 'foo' % {'bar': 'baz'} + URL('https://www.python.org/foo?bar=baz') + +Strings passed to constructor and modification methods are +automatically encoded giving canonical representation as result: + +.. code-block:: pycon + + >>> url = URL('https://www.python.org/путь') + >>> url + URL('https://www.python.org/%D0%BF%D1%83%D1%82%D1%8C') + +Regular properties are *percent-decoded*, use ``raw_`` versions for +getting *encoded* strings: + +.. code-block:: pycon + + >>> url.path + '/путь' + + >>> url.raw_path + '/%D0%BF%D1%83%D1%82%D1%8C' + +Human readable representation of URL is available as ``.human_repr()``: + +.. code-block:: pycon + + >>> url.human_repr() + 'https://www.python.org/путь' + +For full documentation please read https://yarl.readthedocs.org. + + +Installation +------------ + +:: + + $ pip install yarl + +The library is Python 3 only! + +PyPI contains binary wheels for Linux, Windows and MacOS. If you want to install +``yarl`` on another operating system (like *Alpine Linux*, which is not +manylinux-compliant because of the missing glibc and therefore, cannot be +used with our wheels) the the tarball will be used to compile the library from +the source code. It requires a C compiler and and Python headers installed. + +To skip the compilation you must explicitly opt-in by setting the `YARL_NO_EXTENSIONS` +environment variable to a non-empty value, e.g.: + +.. code-block:: bash + + $ YARL_NO_EXTENSIONS=1 pip install yarl + +Please note that the pure-Python (uncompiled) version is much slower. However, +PyPy always uses a pure-Python implementation, and, as such, it is unaffected +by this variable. + +Dependencies +------------ + +YARL requires multidict_ library. + + +API documentation +------------------ + +The documentation is located at https://yarl.readthedocs.org + + +Why isn't boolean supported by the URL query API? +------------------------------------------------- + +There is no standard for boolean representation of boolean values. + +Some systems prefer ``true``/``false``, others like ``yes``/``no``, ``on``/``off``, +``Y``/``N``, ``1``/``0``, etc. + +``yarl`` cannot make an unambiguous decision on how to serialize ``bool`` values because +it is specific to how the end-user's application is built and would be different for +different apps. The library doesn't accept booleans in the API; a user should convert +bools into strings using own preferred translation protocol. + + +Comparison with other URL libraries +------------------------------------ + +* furl (https://pypi.python.org/pypi/furl) + + The library has rich functionality but the ``furl`` object is mutable. + + I'm afraid to pass this object into foreign code: who knows if the + code will modify my url in a terrible way while I just want to send URL + with handy helpers for accessing URL properties. + + ``furl`` has other non-obvious tricky things but the main objection + is mutability. + +* URLObject (https://pypi.python.org/pypi/URLObject) + + URLObject is immutable, that's pretty good. + + Every URL change generates a new URL object. + + But the library doesn't do any decode/encode transformations leaving the + end user to cope with these gory details. + + +Source code +----------- + +The project is hosted on GitHub_ + +Please file an issue on the `bug tracker +`_ if you have found a bug +or have some suggestion in order to improve the library. + +The library uses `Azure Pipelines `_ for +Continuous Integration. + +Discussion list +--------------- + +*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs + +Feel free to post your questions and ideas here. + + +Authors and License +------------------- + +The ``yarl`` package is written by Andrew Svetlov. + +It's *Apache 2* licensed and freely available. + + +.. _GitHub: https://github.com/aio-libs/yarl + +.. _multidict: https://github.com/aio-libs/multidict + + +========= +Changelog +========= + +.. + You should *NOT* be adding new change log entries to this file, this + file is managed by towncrier. You *may* edit previous change logs to + fix problems like typo corrections or such. + To add a new change log entry, please see + https://pip.pypa.io/en/latest/development/#adding-a-news-entry + we named the news folder "changes". + + WARNING: Don't drop the next directive! + +.. towncrier release notes start + +1.8.2 (2022-12-03) +================== + +This is the first release that started shipping wheels for Python 3.11. + + +1.8.1 (2022-08-01) +================== + +Misc +---- + +- `#694 `_, `#699 `_, `#700 `_, `#701 `_, `#702 `_, `#703 `_, `#739 `_ + + +1.8.0 (2022-08-01) +================== + +Features +-------- + +- Added ``URL.raw_suffix``, ``URL.suffix``, ``URL.raw_suffixes``, ``URL.suffixes``, ``URL.with_suffix``. (`#613 `_) + + +Improved Documentation +---------------------- + +- Fixed broken internal references to ``(?P=rendered_text)``. (`#665 `_) +- Fixed broken external references to ``(?P=rendered_text)`` docs. (`#665 `_) + + +Deprecations and Removals +------------------------- + +- Dropped Python 3.6 support. (`#672 `_) + + +Misc +---- + +- `#646 `_, `#699 `_, `#701 `_ + + +1.7.2 (2021-11-01) +================== + +Bugfixes +-------- + +- Changed call in ``with_port()`` to stop reencoding parts of the URL that were already encoded. (`#623 `_) + + +1.7.1 (2021-10-07) +================== + +Bugfixes +-------- + +- Fix 1.7.0 build error + +1.7.0 (2021-10-06) +================== + +Features +-------- + +- Add `__bytes__()` magic method so that `bytes(url)` will work and use optimal ASCII encoding. (`#582 `_) +- Started shipping platform-specific arm64 wheels for Apple Silicon. (`#622 `_) +- Started shipping platform-specific wheels with the ``musl`` tag targeting typical Alpine Linux runtimes. (`#622 `_) +- Added support for Python 3.10. (`#622 `_) + + +1.6.3 (2020-11-14) +================== + +Bugfixes +-------- + +- No longer loose characters when decoding incorrect percent-sequences (like ``%e2%82%f8``). All non-decodable percent-sequences are now preserved. + `#517 `_ +- Provide x86 Windows wheels. + `#535 `_ + + +---- + + +1.6.2 (2020-10-12) +================== + + +Bugfixes +-------- + +- Provide generated ``.c`` files in TarBall distribution. + `#530 `_ + +1.6.1 (2020-10-12) +================== + +Features +-------- + +- Provide wheels for ``aarch64``, ``i686``, ``ppc64le``, ``s390x`` architectures on + Linux as well as ``x86_64``. + `#507 `_ +- Provide wheels for Python 3.9. + `#526 `_ + +Bugfixes +-------- + +- ``human_repr()`` now always produces valid representation equivalent to the original URL (if the original URL is valid). + `#511 `_ +- Fixed requoting a single percent followed by a percent-encoded character in the Cython implementation. + `#514 `_ +- Fix ValueError when decoding ``%`` which is not followed by two hexadecimal digits. + `#516 `_ +- Fix decoding ``%`` followed by a space and hexadecimal digit. + `#520 `_ +- Fix annotation of ``with_query()``/``update_query()`` methods for ``key=[val1, val2]`` case. + `#528 `_ + +Removal +------- + +- Drop Python 3.5 support; Python 3.6 is the minimal supported Python version. + + +---- + + +1.6.0 (2020-09-23) +================== + +Features +-------- + +- Allow for int and float subclasses in query, while still denying bool. + `#492 `_ + + +Bugfixes +-------- + +- Do not requote arguments in ``URL.build()``, ``with_xxx()`` and in ``/`` operator. + `#502 `_ +- Keep IPv6 brackets in ``origin()``. + `#504 `_ + + +---- + + +1.5.1 (2020-08-01) +================== + +Bugfixes +-------- + +- Fix including relocated internal ``yarl._quoting_c`` C-extension into published PyPI dists. + `#485 `_ + + +Misc +---- + +- `#484 `_ + + +---- + + +1.5.0 (2020-07-26) +================== + +Features +-------- + +- Convert host to lowercase on URL building. + `#386 `_ +- Allow using ``mod`` operator (`%`) for updating query string (an alias for ``update_query()`` method). + `#435 `_ +- Allow use of sequences such as ``list`` and ``tuple`` in the values + of a mapping such as ``dict`` to represent that a key has many values:: + + url = URL("http://example.com") + assert url.with_query({"a": [1, 2]}) == URL("http://example.com/?a=1&a=2") + + `#443 `_ +- Support URL.build() with scheme and path (creates a relative URL). + `#464 `_ +- Cache slow IDNA encode/decode calls. + `#476 `_ +- Add ``@final`` / ``Final`` type hints + `#477 `_ +- Support URL authority/raw_authority properties and authority argument of ``URL.build()`` method. + `#478 `_ +- Hide the library implementation details, make the exposed public list very clean. + `#483 `_ + + +Bugfixes +-------- + +- Fix tests with newer Python (3.7.6, 3.8.1 and 3.9.0+). + `#409 `_ +- Fix a bug where query component, passed in a form of mapping or sequence, is unquoted in unexpected way. + `#426 `_ +- Hide `Query` and `QueryVariable` type aliases in `__init__.pyi`, now they are prefixed with underscore. + `#431 `_ +- Keep ipv6 brackets after updating port/user/password. + `#451 `_ + + +---- + + +1.4.2 (2019-12-05) +================== + +Features +-------- + +- Workaround for missing `str.isascii()` in Python 3.6 + `#389 `_ + + +---- + + +1.4.1 (2019-11-29) +================== + +* Fix regression, make the library work on Python 3.5 and 3.6 again. + +1.4.0 (2019-11-29) +================== + +* Distinguish an empty password in URL from a password not provided at all (#262) + +* Fixed annotations for optional parameters of ``URL.build`` (#309) + +* Use None as default value of ``user`` parameter of ``URL.build`` (#309) + +* Enforce building C Accelerated modules when installing from source tarball, use + ``YARL_NO_EXTENSIONS`` environment variable for falling back to (slower) Pure Python + implementation (#329) + +* Drop Python 3.5 support + +* Fix quoting of plus in path by pure python version (#339) + +* Don't create a new URL if fragment is unchanged (#292) + +* Included in error msg the path that produces starting slash forbidden error (#376) + +* Skip slow IDNA encoding for ASCII-only strings (#387) + + +1.3.0 (2018-12-11) +================== + +* Fix annotations for ``query`` parameter (#207) + +* An incoming query sequence can have int variables (the same as for + Mapping type) (#208) + +* Add ``URL.explicit_port`` property (#218) + +* Give a friendlier error when port can't be converted to int (#168) + +* ``bool(URL())`` now returns ``False`` (#272) + +1.2.6 (2018-06-14) +================== + +* Drop Python 3.4 trove classifier (#205) + +1.2.5 (2018-05-23) +================== + +* Fix annotations for ``build`` (#199) + +1.2.4 (2018-05-08) +================== + +* Fix annotations for ``cached_property`` (#195) + +1.2.3 (2018-05-03) +================== + +* Accept ``str`` subclasses in ``URL`` constructor (#190) + +1.2.2 (2018-05-01) +================== + +* Fix build + +1.2.1 (2018-04-30) +================== + +* Pin minimal required Python to 3.5.3 (#189) + +1.2.0 (2018-04-30) +================== + +* Forbid inheritance, replace ``__init__`` with ``__new__`` (#171) + +* Support PEP-561 (provide type hinting marker) (#182) + +1.1.1 (2018-02-17) +================== + +* Fix performance regression: don't encode empty netloc (#170) + +1.1.0 (2018-01-21) +================== + +* Make pure Python quoter consistent with Cython version (#162) + +1.0.0 (2018-01-15) +================== + +* Use fast path if quoted string does not need requoting (#154) + +* Speed up quoting/unquoting by ``_Quoter`` and ``_Unquoter`` classes (#155) + +* Drop ``yarl.quote`` and ``yarl.unquote`` public functions (#155) + +* Add custom string writer, reuse static buffer if available (#157) + Code is 50-80 times faster than Pure Python version (was 4-5 times faster) + +* Don't recode IP zone (#144) + +* Support ``encoded=True`` in ``yarl.URL.build()`` (#158) + +* Fix updating query with multiple keys (#160) + +0.18.0 (2018-01-10) +=================== + +* Fallback to IDNA 2003 if domain name is not IDNA 2008 compatible (#152) + +0.17.0 (2017-12-30) +=================== + +* Use IDNA 2008 for domain name processing (#149) + +0.16.0 (2017-12-07) +=================== + +* Fix raising ``TypeError`` by ``url.query_string()`` after + ``url.with_query({})`` (empty mapping) (#141) + +0.15.0 (2017-11-23) +=================== + +* Add ``raw_path_qs`` attribute (#137) + +0.14.2 (2017-11-14) +=================== + +* Restore ``strict`` parameter as no-op in ``quote`` / ``unquote`` + +0.14.1 (2017-11-13) +=================== + +* Restore ``strict`` parameter as no-op for sake of compatibility with + aiohttp 2.2 + +0.14.0 (2017-11-11) +=================== + +* Drop strict mode (#123) + +* Fix ``"ValueError: Unallowed PCT %"`` when there's a ``"%"`` in the url (#124) + +0.13.0 (2017-10-01) +=================== + +* Document ``encoded`` parameter (#102) + +* Support relative urls like ``'?key=value'`` (#100) + +* Unsafe encoding for QS fixed. Encode ``;`` char in value param (#104) + +* Process passwords without user names (#95) + +0.12.0 (2017-06-26) +=================== + +* Properly support paths without leading slash in ``URL.with_path()`` (#90) + +* Enable type annotation checks + +0.11.0 (2017-06-26) +=================== + +* Normalize path (#86) + +* Clear query and fragment parts in ``.with_path()`` (#85) + +0.10.3 (2017-06-13) +=================== + +* Prevent double URL args unquoting (#83) + +0.10.2 (2017-05-05) +=================== + +* Unexpected hash behaviour (#75) + + +0.10.1 (2017-05-03) +=================== + +* Unexpected compare behaviour (#73) + +* Do not quote or unquote + if not a query string. (#74) + + +0.10.0 (2017-03-14) +=================== + +* Added ``URL.build`` class method (#58) + +* Added ``path_qs`` attribute (#42) + + +0.9.8 (2017-02-16) +================== + +* Do not quote ``:`` in path + + +0.9.7 (2017-02-16) +================== + +* Load from pickle without _cache (#56) + +* Percent-encoded pluses in path variables become spaces (#59) + + +0.9.6 (2017-02-15) +================== + +* Revert backward incompatible change (BaseURL) + + +0.9.5 (2017-02-14) +================== + +* Fix BaseURL rich comparison support + + +0.9.4 (2017-02-14) +================== + +* Use BaseURL + + +0.9.3 (2017-02-14) +================== + +* Added BaseURL + + +0.9.2 (2017-02-08) +================== + +* Remove debug print + + +0.9.1 (2017-02-07) +================== + +* Do not lose tail chars (#45) + + +0.9.0 (2017-02-07) +================== + +* Allow to quote ``%`` in non strict mode (#21) + +* Incorrect parsing of query parameters with %3B (;) inside (#34) + +* Fix core dumps (#41) + +* tmpbuf - compiling error (#43) + +* Added ``URL.update_path()`` method + +* Added ``URL.update_query()`` method (#47) + + +0.8.1 (2016-12-03) +================== + +* Fix broken aiohttp: revert back ``quote`` / ``unquote``. + + +0.8.0 (2016-12-03) +================== + +* Support more verbose error messages in ``.with_query()`` (#24) + +* Don't percent-encode ``@`` and ``:`` in path (#32) + +* Don't expose ``yarl.quote`` and ``yarl.unquote``, these functions are + part of private API + +0.7.1 (2016-11-18) +================== + +* Accept not only ``str`` but all classes inherited from ``str`` also (#25) + +0.7.0 (2016-11-07) +================== + +* Accept ``int`` as value for ``.with_query()`` + +0.6.0 (2016-11-07) +================== + +* Explicitly use UTF8 encoding in setup.py (#20) +* Properly unquote non-UTF8 strings (#19) + +0.5.3 (2016-11-02) +================== + +* Don't use namedtuple fields but indexes on URL construction + +0.5.2 (2016-11-02) +================== + +* Inline ``_encode`` class method + +0.5.1 (2016-11-02) +================== + +* Make URL construction faster by removing extra classmethod calls + +0.5.0 (2016-11-02) +================== + +* Add cython optimization for quoting/unquoting +* Provide binary wheels + +0.4.3 (2016-09-29) +================== + +* Fix typing stubs + +0.4.2 (2016-09-29) +================== + +* Expose ``quote()`` and ``unquote()`` as public API + +0.4.1 (2016-09-28) +================== + +* Support empty values in query (``'/path?arg'``) + +0.4.0 (2016-09-27) +================== + +* Introduce ``relative()`` (#16) + +0.3.2 (2016-09-27) +================== + +* Typo fixes #15 + +0.3.1 (2016-09-26) +================== + +* Support sequence of pairs as ``with_query()`` parameter + +0.3.0 (2016-09-26) +================== + +* Introduce ``is_default_port()`` + +0.2.1 (2016-09-26) +================== + +* Raise ValueError for URLs like 'http://:8080/' + +0.2.0 (2016-09-18) +================== + +* Avoid doubling slashes when joining paths (#13) + +* Appending path starting from slash is forbidden (#12) + +0.1.4 (2016-09-09) +================== + +* Add kwargs support for ``with_query()`` (#10) + +0.1.3 (2016-09-07) +================== + +* Document ``with_query()``, ``with_fragment()`` and ``origin()`` + +* Allow ``None`` for ``with_query()`` and ``with_fragment()`` + +0.1.2 (2016-09-07) +================== + +* Fix links, tune docs theme. + +0.1.1 (2016-09-06) +================== + +* Update README, old version used obsolete API + +0.1.0 (2016-09-06) +================== + +* The library was deeply refactored, bytes are gone away but all + accepted strings are encoded if needed. + +0.0.1 (2016-08-30) +================== + +* The first release. diff --git a/lib/yarl-1.8.2.dist-info/RECORD b/lib/yarl-1.8.2.dist-info/RECORD new file mode 100644 index 0000000..a0e7e4c --- /dev/null +++ b/lib/yarl-1.8.2.dist-info/RECORD @@ -0,0 +1,19 @@ +yarl-1.8.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +yarl-1.8.2.dist-info/LICENSE,sha256=3eoOXrmfMPhga4AoMO9cINtUPnV4jyYhn5px2q_EfgA,622 +yarl-1.8.2.dist-info/METADATA,sha256=umdPRCkUD896BjdonT2TU1eqZlKnMPBBsrvUjTRnn48,22154 +yarl-1.8.2.dist-info/RECORD,, +yarl-1.8.2.dist-info/WHEEL,sha256=J_4V_gB-O6Y7Pn6lk91K27JaIhI-q07YM5J8Ufzqla4,100 +yarl-1.8.2.dist-info/top_level.txt,sha256=vf3SJuQh-k7YtvsUrV_OPOrT9Kqn0COlk7IPYyhtGkQ,5 +yarl/__init__.py,sha256=q8pUVrk1FmEvsKfGMcwSnFD_tpRao3A65HSxsceIHFM,159 +yarl/__init__.pyi,sha256=TrQMldy6mtZDymOLfGvpDA9Ln3ME-Swllin9uwHhLrg,4001 +yarl/__pycache__/__init__.cpython-39.pyc,, +yarl/__pycache__/_quoting.cpython-39.pyc,, +yarl/__pycache__/_quoting_py.cpython-39.pyc,, +yarl/__pycache__/_url.cpython-39.pyc,, +yarl/_quoting.py,sha256=JSWI4rNhpLOLNxs2w_i0b0IE1N1CJaWnOs3eg4wJ7RU,537 +yarl/_quoting_c.cp39-win_amd64.pyd,sha256=TekGLVm61L2ptouxLHu8Htm3HTlTUvLOcRAD0xqy9Ok,68608 +yarl/_quoting_c.pyi,sha256=8QHtEuD1IwrQKNfZU8VAykdBOF9TqF6DFc6jmPbM6mo,463 +yarl/_quoting_c.pyx,sha256=puLJSZV8xTibAlMNSzpKCIwPz0A6KccYYztMzbSfHbU,11869 +yarl/_quoting_py.py,sha256=pU6oGMXaI4yIrGT_S9hdN_8sjiMR7USAjTBXMBu3hXY,6567 +yarl/_url.py,sha256=PLwTy9cYgydtkfVDh3qFsWK5M_BHJ33cUd-qlH_xdxY,37585 +yarl/py.typed,sha256=zjQ6gjHjUDJJ2--T0_QyNmdMplcicI7420ML8eglns8,15 diff --git a/lib/yarl-1.8.2.dist-info/WHEEL b/lib/yarl-1.8.2.dist-info/WHEEL new file mode 100644 index 0000000..d22c9ab --- /dev/null +++ b/lib/yarl-1.8.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.38.4) +Root-Is-Purelib: false +Tag: cp39-cp39-win_amd64 + diff --git a/lib/yarl-1.8.2.dist-info/top_level.txt b/lib/yarl-1.8.2.dist-info/top_level.txt new file mode 100644 index 0000000..e93e8bd --- /dev/null +++ b/lib/yarl-1.8.2.dist-info/top_level.txt @@ -0,0 +1 @@ +yarl diff --git a/lib/yarl/__init__.py b/lib/yarl/__init__.py new file mode 100644 index 0000000..c2df0c6 --- /dev/null +++ b/lib/yarl/__init__.py @@ -0,0 +1,5 @@ +from ._url import URL, cache_clear, cache_configure, cache_info + +__version__ = "1.8.2" + +__all__ = ("URL", "cache_clear", "cache_configure", "cache_info") diff --git a/lib/yarl/__init__.pyi b/lib/yarl/__init__.pyi new file mode 100644 index 0000000..fc761b5 --- /dev/null +++ b/lib/yarl/__init__.pyi @@ -0,0 +1,118 @@ +import sys +from functools import _CacheInfo +from typing import Any, Mapping, Optional, Sequence, Tuple, Type, Union, overload + +import multidict + +if sys.version_info >= (3, 8): + from typing import Final, TypedDict, final +else: + from typing_extensions import Final, TypedDict, final + +_SimpleQuery = Union[str, int, float] +_QueryVariable = Union[_SimpleQuery, Sequence[_SimpleQuery]] +_Query = Union[ + None, str, Mapping[str, _QueryVariable], Sequence[Tuple[str, _QueryVariable]] +] + +@final +class URL: + scheme: Final[str] + raw_user: Final[str] + user: Final[Optional[str]] + raw_password: Final[Optional[str]] + password: Final[Optional[str]] + raw_host: Final[Optional[str]] + host: Final[Optional[str]] + port: Final[Optional[int]] + raw_authority: Final[str] + authority: Final[str] + raw_path: Final[str] + path: Final[str] + raw_query_string: Final[str] + query_string: Final[str] + path_qs: Final[str] + raw_path_qs: Final[str] + raw_fragment: Final[str] + fragment: Final[str] + query: Final[multidict.MultiDict[str]] + raw_name: Final[str] + name: Final[str] + raw_suffix: Final[str] + suffix: Final[str] + raw_suffixes: Final[Tuple[str, ...]] + suffixes: Final[Tuple[str, ...]] + raw_parts: Final[Tuple[str, ...]] + parts: Final[Tuple[str, ...]] + parent: Final[URL] + def __init__( + self, val: Union[str, "URL"] = ..., *, encoded: bool = ... + ) -> None: ... + @classmethod + def build( + cls, + *, + scheme: str = ..., + authority: str = ..., + user: Optional[str] = ..., + password: Optional[str] = ..., + host: str = ..., + port: Optional[int] = ..., + path: str = ..., + query: Optional[_Query] = ..., + query_string: str = ..., + fragment: str = ..., + encoded: bool = ... + ) -> URL: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __le__(self, other: Any) -> bool: ... + def __lt__(self, other: Any) -> bool: ... + def __ge__(self, other: Any) -> bool: ... + def __gt__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + def __truediv__(self, name: str) -> URL: ... + def __mod__(self, query: _Query) -> URL: ... + def is_absolute(self) -> bool: ... + def is_default_port(self) -> bool: ... + def origin(self) -> URL: ... + def relative(self) -> URL: ... + def with_scheme(self, scheme: str) -> URL: ... + def with_user(self, user: Optional[str]) -> URL: ... + def with_password(self, password: Optional[str]) -> URL: ... + def with_host(self, host: str) -> URL: ... + def with_port(self, port: Optional[int]) -> URL: ... + def with_path(self, path: str, *, encoded: bool = ...) -> URL: ... + @overload + def with_query(self, query: _Query) -> URL: ... + @overload + def with_query(self, **kwargs: _QueryVariable) -> URL: ... + @overload + def update_query(self, query: _Query) -> URL: ... + @overload + def update_query(self, **kwargs: _QueryVariable) -> URL: ... + def with_fragment(self, fragment: Optional[str]) -> URL: ... + def with_name(self, name: str) -> URL: ... + def with_suffix(self, suffix: str) -> URL: ... + def join(self, url: URL) -> URL: ... + def human_repr(self) -> str: ... + # private API + @classmethod + def _normalize_path(cls, path: str) -> str: ... + +@final +class cached_property: + def __init__(self, wrapped: Any) -> None: ... + def __get__(self, inst: URL, owner: Type[URL]) -> Any: ... + def __set__(self, inst: URL, value: Any) -> None: ... + +class CacheInfo(TypedDict): + idna_encode: _CacheInfo + idna_decode: _CacheInfo + +def cache_clear() -> None: ... +def cache_info() -> CacheInfo: ... +def cache_configure( + *, idna_encode_size: Optional[int] = ..., idna_decode_size: Optional[int] = ... +) -> None: ... diff --git a/lib/yarl/__pycache__/__init__.cpython-39.pyc b/lib/yarl/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1044f07a96673207a1b4d28cff398d08f22651f3 GIT binary patch literal 307 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 Ps9oZog~W)0RsQVXW5?6{ literal 0 HcmV?d00001 diff --git a/lib/yarl/__pycache__/_url.cpython-39.pyc b/lib/yarl/__pycache__/_url.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f4af1af4f32fc0bb77f30629ed4b009c3ff7435 GIT binary patch literal 28027 zcmb__dypK*dEdn;^M7LIe&358y)-1%V_WJV;RF5yXR!70Bh@_1s|( zd%K68Ie@p^lNbUPDOr#yTef78lmS|n1VN`?8M($M^HX;_}ywN<56cqd&OnO|L6JwI9+MR`UI)UKOfQ(7~>wzL-aS(TGt z{&lys&Rc)pDsAxAd+XKU>y9d@p$l$l$N`fQjz=ABve>eJr19p_qu-Hl@Q{`9xh>)o&faqw052VcZSfQ$es0rkVPo zbIuP_=g>1JOf{zF!i@HUMO~j}Tk${U9sKs==aX=h-LlG7%fi^5$&$NN(KU{J z#hORqw)23)Ql+Q0kc3E$KH$z#*Hx^VJ<=bsK53nyn6g0g?AqC9;v@ciJ; zlaExVPWsiryM3WDeY!I1`6t=-&N3h{v9J^lYww)KXK|c`rECP(|fUH*_Uv=!s(Sa8P9Z3Px0{)aDz0Q1qU!Ob4~4 zVy$}G%NOx4IOP?OEaBC9@wvTm`_HwT*-@NY42spDxUd-bQXA9*ycZiYypuMfPoq-w zb$qW{4;oUvTK9tr$gikCD8zdkeM7G^p3q zT&&{(I@tArbJgHfv4X;=eQD%uMOQ0RHO~yb>hHkgcz7W28auKBAXAk9iRY6B2&=rj zJx$Vz|1qD%Z$Eziy+~R@YRy8+o@35_(yDK8t-x8@Zd?2>q-Z~F)tz0aJC{NoR~f<# zt?_g?a86@NdJ40mtQynhayYy%2y_+j^$u#?&|$V*u2<$kw4uu@CW7mGwHbW_zAofz zl8v}!-Dij`2{2-J;yG{|K^4sikXP3PYac@I{uNl@?OUHQDj=6%0){TIH52G z>k>vf=d@Cnon^P>wo;3>-qv#Q%(|3n*_Ul8MLpKqWAVuqdlC~nQaICxC@E%H?f%(H}u#*;(7QM7ABPFx~;Oo5o62>d6qq*6%QjDiSvFd{iE9&+}y|x48{W2-iazcKWO#spwyWh&*~y-==Dg#Lb-fqu~IVxpl@XPEiBK*;L*3S z>~Lyjhb-lV-jp4dM-J-VOx>0>V-HPibwN-7y^?Eg~w&S{4-KKWndV{)M zP2hT?+NpNodXu_C-HB__yII}!x~=Y3AB3zQQ}?J3q4XAYulg{qThwm#5nMl@_Ne=C zy;a?>9>8_0+N<{Ay3HF`A65HBes4$qfI2Ao+q@m>ka|$^x1;1^1DzzsUDO39VkDd9+&)`C_k!>N&YUBA6F+Ne>d_^s3#@=LFAuOPfPwD)Gw)z zOa4R1pH$CC{$A91R+T0HVU$0oDw5xg{FIuO{6~;i%9H#a)SpqalD`k-r&Lw)_ai^2 zPD}m)7nY&#H5hKZyMEYDw~kkZ-CN zB>$lIkZP%A$sY!-d_sLv@(&~bHT9zUCl@f)OCQ7C@2i*8dAWN;T~NOQygaHtrC!E$ zQoW)+jq79TGwN4yJ)%CVF5>z)sO)pV`md>1FF2*6Xyfzh*QNDi`08({FG&8l`c3s) zsC`0xQGE&5C)97NFXQ^8dQH8K>r?6t^%Y#7R==Yz;aXCc)tk6}T>Y;4Dy}Eh8nB_S zhp7`s9~pP_2QWE9w+s$*2-?hmh6V>Us_E?A-E?-|HlBi9sLXiK*))o$15au48@zZI zF8XdHP4|JFyZ1KT`)<4SBgePssHQDf`T076DVl-LlZE#Rp| z0W{WvHf!#K6z=a0Ts~X2>${`UG)j-Q9A5_+h1vg-T`vX$Ejvh^cl2D~1`to@%^TSu zt5U4da$dFlBM@=!MzB7WUIuSlPS@uk=yJ>HR$4^gCA61e?Peb3`Q;46`~<{&X6XmE zwG4Zrxehf3(9eOFoF}cPErrrg4Ak@Hy32y_x8?@%E)|8LR=Sk|rv$IOn1QggE)C=R z`HdDtXSU_Oit(dtRVyp^*4zl{XTeb&n<92K>K3GK4tJxt8$Z{1|>x(LC`FygF(JkXX_2^spgjbm3qAa zfK5*~>Sv+&;yq}UP%XV# zPalVVlrbbH??751rV{m@xG8ekR(t;PJ z*!3K%Q6mcTPf)ZQoh)DVV+}LR9Y^IzRpUn}tMMW96w-UXa@s4`y`a{ZhQ$OEL!M?A zpqrm-XcZ2ZkL)}CQ2FS=$4@+V{NT}WRT;pa2ZWklnSBk5obOd=re3+S2!$Nx#S+Yo zY8hZMHCB}$KXLHr)9p4!%ZHBcd+^bNlgHa-8Q4zX3Dh#{-nnvF-@$Q_ql_2A)O5|) z+gU)3R(}{tm@%E!_p#(KOKeZy&npZ;PcMbp8C{v3hmvNrpSJQLddD%;yojHF6bY3x zDrGKy=9QP?ta%{mTCp{+XaIF9r71{Vt5JDJolG}yc|j3>kP8YhptjNKdr z{Xlz_v5XIbx4;qt*mDVtLYL6ikKpU=F~|V)lPC)dGKw-MKz&v}#cCXUVpk60W)*&k zvi}gdlcP7cQvRBji(eYQjNTlpjL7dm*$&(b=V@j2o>orp!M))#;4q7puUbxY9f+>E z=$hwM*8*Fm4PVkEY5X$y4Ja6f7t_my z`Y!bGd+6i$(4X(MGXD2e2Bn+iofNnOxKC@a08UV_gaZ{?gW!Q1z)iBP0q}}}Rzd$? z+~uM>Yc7v>22?)cK!dFuYLWXCT6yI1E#buQ|$jLngb}kEzM#b%*PxJg{scE_TT@*A-`UV_s+yvJWC} zstUci4pb67xCrdJ8EUvXGS&UXDe>%-%PI6CHYV2^b||)gyGB=^DF-R|_T%SM83a49 zmTjmbw!Z^Y*|}_ifx$!J7m>P>Pb*tF7olJU87fY&<5QDY?VTVhN`mnLBSl0i3|4)L zB$3?0VFjOXNEok;r>W*$ag4AKS<^E>NG8y1xt!s!$aU$t*hEu4;`Q>hYe-#aU~?SO zoc$V*0eukuQRJLA$ZvoOXWQ1MvvnM5a;{vCKxho@1MHdIPmIx@g8rjdg$`gB46x0E z6&*@oJd9=obm_cpR9SSxnGDkvf4W-5eC1bV(&`3tJ?v}`<2wNiKvZCIWk z!Cm}3)q=z4G}_U#XdI{~9qC<2!r{qAaCm;91_2BmOtpzZ&#}&F)^`QG@b17ns7o$% znmZ)Dvv=T`a@jjmF2mJs`J;H#qTT@gVAw81X)|x2z-nF4=kOB{kiU@}4D3ngqR1aY znIm9G`F8^hF2Ha%z(98fTpY^|C>`S4<;=j_%7*`nE_8Pi8f3*krB9(sEn-Q^@N?jP|gyxTk z@Tpw)!#J^e8UxTGNqk`Z+(Hem|d{#nA5R|7kw@IX?|n^xmVM#nA4(#;1EnO;JwZ z2n}391|$uFg-!;7H>9t(KzGFKhHy$5;Y3a_=W-G#`?RC)hv>4`Tg$2XJ+PdQ7~Vi0 zfRZgO=Mp%;W&6?qQ#y8`BZ^(FZ0LtB%HYx#Zt*Ex^Kc5IBp8%$6kyGZukd0DdMiAH zm`K@4nL^D~`{4Dii$zI0r`Wt13Qk3fq!jV!WBuY3HL65FipFy#)mGv~#2nYpV%+0H zx{OPhmqvW5B1Yrso|#xaq8f$OC9`yuq3^w}LYGTgM%f`tGd#=qrH!LnWL%c!hFE23 zSj>UnJ6i_JWmv+Vs-7*E?;{2|=`89L&`|R47!8F65Ooli(kybIi!@i636;FPg1!>I zMll*AIie=`UJ8o~Vj>#tE0VIbb9%c7DT_6*a+ym-vGNefS1E_~u@0|w@l{l9Ga3$w zQfMeK;;1)vu2WR*ro&h1T7rwSQh}*z`!n-I-oLQP||+bg%jc0=IJ`u@h%dr;N=UPin1^r)T`rKB z?o-^v0wpayF2Uyp13~p-8H2)FLNusnI4Gk~ZE{9nKsTeIwX3XvWg=G$xls@5WazF3L&-00J5@PLh1gg}wAp0RqDa=;*nrr1 zJ9m!nC?2ZRU;&BRaIWfm9Uqwl-Dh>>oH2I#;8tX@m!NdO%(4zWwQJa2`Z1XRNeIKX z^soZs3hYZzDd))Es7tW?R+h6?3v3j?Nj3kZ6FAD7sc=b@7H3DdJ7H6uk*DNxacz3Q z`~`2iI#Y#T%Yeu%g%U2_#Oy?ID_6#M@7%c{3$zmrJ-d}aTv@#P-d$TG2quv23&}&E zHZlH&4jeqR@5Cd=%SRqNdiv$C7Ynf5niG?Rc2+9{d~gjkBKWc>U@+ezfgNh)t(vXF28x{HNmCniSy&H1%Pk zkp#>c-SZ&8X846z-_(QkLrjQZ5p1Hhqbb3tebzL&olkqUKX@BwtMz3fQx;hVA`=_x z=^84hq6eBvU3lbNA})m;??9$M9-(tn>jnnt$mgP>s0f+nsTenaT&=?8e?dYNT|j(w zfHFo}FGlDC5mX2g`=ddBfy1Wq34}MU0=Ug$GVMu`IKYoBKBjDA^$9~bV9fC9B z8GIOl5i)!G`Of+bSsZPDz=4XnG(aa;Q#=Mq&$>6XE2eb%jCRHHK=BqvyNM<__hNVD5X!JQ=i8R!6$giA6KpfloHisg5OP10D z@!=@RPAn?ud#Eq|xh)sdO+#(l0ayEG;2qZ8CC=zQ)e?!vpd4eRe*Y zFXg?N|$eMjeoTH;Ty==ilje;@|Eh!2W>N}X;lCDgux*>(_n3~`aW z{Yd0h4C04LUvq8B=2yG=N*dx%DEn4q$c_>fbd>Xy*8N#a-zcl&P)=!nfiWG3!T*bl z1n%AsU^MJ6Rx_)rUV)xReuu&_7N$TRJyTfL+kVW^aVA;=W|1$8<-Dd$g%x8YE|S?x z^OIwovlrF`4g$)Uho3>_@i&gd3R#4e9i58X?Nb#PL&X_H4^AOIvjZXhF_wtOcqzzx z9s#Y>3~S_Ppa3Rnvk`dmF|HZK+Pc((!`%<6!6Ks(JH83_Es|CAuFqpsZFRaY+hD!F zPiP`*V8st~(F0+{pU^`O+gR!k9d_9C|CbA(&apm=dXB`R!rTUE48TvsqzMAe8<4VSp}n8FxhtS zewpZ8q5laN4Nes}IuTsZm$~ZQp5FXK*YLVXzmMRWj3@CgXE=~xQN%$a#@^&m`k+ia zH5)n*C3APh0EnM-Oplw831BK?&N`wZ!AOI0FHtwblRm$%*c}rNeRTn$!xHN`OZmVF zY^*5Un4lDf4V(BAuRS4?_h24$O*^APVu1m^2WTYV+(kIgi}gmk*-iu>zhZ~0PkY`% zc?xmBrx|Ur==rdY1_>8xQsD?Cp!6vY7a*c^9~zK_ubT-oT0d46CX%cm%F8S8eywTz zSWLnR9Qw$hE=?KZGh?i}=r?k60(57|BzlkG2U2#`LH zA8RXL>~deKJ&6Y&>==uvFz;_1H{w-F8y|trRK%EaEs zxN1KD(a2sHS7#r1hXZ^Un=0;SKS^R%svXo4J-arz-(NRl8oEX|_1E!LbX4H@CVShj zqk~5Glg=LO>8!Y{JD#uT@U_VMMBKH+Mbcl(>ZS0c`nwW2y+(bnaVCNHGObs_AXAz)Ac;3^p z{msgr#oFf?#Vh^QD)lSyUtdab(FaRIPJIH)Nw9h^+q!hkI`C|pH?WGD<|-;CjN6>s zR}2=hILv6hpn?bmcz(Ue9k89$xm_UE>D1V^b?4R{<9#GG_+0c#Um7rrvkW$z9MANM z?3W127m z27ERiSTAEyf`aiv_7&jZ0Ad3oWxXGiRODFrB}1vJj~JQs@vgX!E$f+)W&0KQUAfHn z1a!?a4p%750x14<;SwAM8eLYlmEKYsoK64E&hB?Qv~ z=2+<5O@{8@h}6;dKq-M)A?yC)gN(h8W{-dP znVoS~PQLXH{^?Jm0Q*PY#6=755dOZ4x1zzGX6_{>7m!3g;EpNK*9#0-LqB_gC>X(8 zRwe_jwqS37W}CN*kr&uh>cR-|m9WlxaRX>dT*W2Z#2RrX5TeAAKc*mH=>^^hYO7_eQFcCbWDi-W<2;JyF=hvDPsh#_&lMS zK=uG`BFIKd{>xnOcU)9CjhHSI8n03tc(rGf%|$i!(FT=U?YQ@~CDZbcdu;aGJxhZfUB# zf2axrisARCXtN*u5C|+^{=Ec*PUNtb;;yd|!inV~OSlokh~g&J$nqYR{Uq`MRt%Py z+zTL4BM7krN&uKJ@yw+D6LyQpT=5}d%jeIyH=Y?@Mm!S3E+P&~S?S38lhVKyM}L!z zeV0i$-Rf_l?3?Jl{%6QEZ|Mf8*N?}_41qsFP?<%*g9J!~nVJMc82uQQ1)#CG>eRt8I#D~<`yFBz4k$Lft&L>hu?nu{O6GX ztgz&;c<;<2(y8Sfu%0=O42<$#HIy6Q|!81_I3h1o5v;@==| z1bSMea+q3ZEQI;Pb>%%TJ4*C<)(}~pHetbJRNVb3TQ7Vpb9sol#oDroOG@EEFH9%# z#-9~NNlCmk-WzKa9PQit|llA$Y4JcjvWvO2gGXhrjs<}A^>`7=t%kFX- z&L2?iata%y?h!yD(un_2FLjxc-tq58-h~EmsAXrYry+s3afyy1?2?|qMkxfHqMobg zsJFG!>#Yy*y%aV$5LqvBoqs*H5x_2i-oUvrslS50yv5#KwLgB)Zx|BMBDgEA`Mp z{1^be5V}n)d-Z@DZJOpJEais@+!<9aYEw4NXoz7*#CSVQ6pz#K+O&{AKFNO+pb6{X zdUb0+W5>muRmaXT7;;(cJSox>DzSGtl%}vZ!`1h;ateTgit3)n6=e|lhz4GOvh}dS zqC3=pw0;76#RT}sPa#i_9o7^0X3HUn>myL1U&PZFlbW`E6*W?*k&||FK!S^vg55&P zSM9yn>z!-wJ;HepvbjnBB|0y0&*OOm)qX3RyIG0LpR#=`%$Nfrbf2X{SsnceA$%*U zTQ1}J3l3Kb%|38O_jBbC(8iBB1e2TCr;cq|U3>$LiLem@Cxc=r7sI&xQLtmci6JN? zWPdCG@8CU%*P6>9*u=h+y^Os+7drzPZcPFiqKz$vLq@9!@L}RW1H^@Jp8y<>*cKN( z;r_GcUES3fUCl@#*^^fzC7bCTydQ8&Ow<$`D4i2Htx_nu2j4Y99e} zj84ZCmq05-tK(TVKzMcp`wU?JP%vObBSLyG59YWN3C@QW_&WAjI&+1~2>SsZ(%7L+ z^I@R*Vt+iyYH)&uc2LB=#h_uPW3-|T;pll-SO!{G4jK7EZ^dTl(tBEZ@nTXq2 zDsOj7jd5Ia*58>Ole`Pbg6`2sv<5&OaEhNb*(}F_4ud#4v*!G8>RV zzJ6mo*&=o(Bmm^&ER+cOkQ)BIAO^Fc4$}^vrU99LAOwUXJJ_fO33Cl1c)33jB-nd* zLP)AnQ|eC)omH@VYcWRY3R+kj(?arw(8C9l+o&sX1MfQJ^?w2o6N=~|h8{W)CxA@- zHZgn*uXj*HWZelJ2u1V|!;qjg?&&TPgeVp{5&(jmBVypGBZv|+q3T2fCI;bkd}=b)?6BVn7bGq@PvdIR&ax__a_8dCx3S$kScu%;8dlKP`OzX zI=CV>poedpZG96AI^Q;)sNC_+zBV6fGY$E$VH%y?d|w38+#J1vAVhYtTZVL!PeEdi z`^t-JQjQRc@REpfs4Df{>tfgl`*N66!^J`?A4?F5=`Na)$R(i}zHx1OnL;bVLTv1G zh^4jcc`)maSh?wBA9ue8`-qqa@;954Xdkyr%afK#YT zqD(eT+(A1k6nokkqrf+I6lbv)zj;UJ=iV3gJMlYYV#-r!*GP!{#8q1`G~C2wSd!U7EfLcH2g=Xu9HV zjX3Xs>_0KEq{U^d-EJ)_1|DrVv(>(bw?@FjHv5# z;xvA~LLydf>7+x$3^T(GpkK{U!}@5;h5cK^=@w$FiLNhtZ7Y2lI&S2Yy73Hxa%s$U zJcrerJdcmDx@zACHG*;{bZb?3l>U&}e6(VIwxVMjDr7l!&ftizFm>8nGECw(QQE6* zj>OCD*yAVlKS6NhQPXk?qRkhbrmRx;R$SpaN<4fo;blMpP8U!@tZS~LyI<~jLdcQ8 zy##F@i(Gf&G!N1CQ38pAk^}LHB7p-sixzI=%`D!mLwC)=wyNVPR1veR6XaFu5_Up? zUWEfi8$Fsg%+wl{VB6MeeTM!KqtW||3!DZxcO!mW#}4>Dj~|V72S8R%H7imq>@J#! zhc@p3B6!Z3FcA5m;UF*Etd}K@n)}sXSROCPmOz{V=*Rnph5d(Vc_(z|L1tl^$6d@s zHgwfT&-8x*IQ3_kP{jx{5P7Hy6GHGQHgh*|>%T?4mx;$BG8WVX*>fEcH@)to_&M-K zxNke}7(a2FhPv=ddxDg{3O}Dzg>xB1d5oNB8M~?9bcnzHGg0&p14_;R3cM(2p!_XYp3f^bEF3eo~lD z^Fs%*r48}7r*S~ZtORy5z!uA@NrZ(7mMP9JV$Ep^0rMuT#_TAfhIZAywYe`Q9`lx= z9NA8c6aBE?xT1AsNfxK(v1LIoovq?L28PZ9y=Iqs7b1X7VbOOH5j%(o>zOTJIB+2V zV35x(n@9VNn>#vqs=|)qJhlN+GlH!3qjNDiN49}U?JyH-9O4>$ zi?bEcg|xkK#=&>k!Glah>t>h2(dd|jBm0gXkNqAOSmnojn#SRf^S)8Ao|6Irewa&u zALiSTH!_+)j_kD1CGx|JAp*0_USRWgsG|uWgm-lTe+6KgaE$ z457ik9ZBJyQualxP;uu69Y>XqL)!P3z6osyZ5iCwj`vMZdkff^Q>iZ%qjOD*+hm6n z_sjvSJcyLpi6rI>3=xiM9QWwA)ovo4L;x3hZ1iKM@%W9%fa_!L?Ry_|l5ljM$9c}H z)jHe(hY?*`^BX+Ms2JdgAdCSYoT>)8qL+&G#rY{rbxB|(fPz0WUbgC@c;Py?~#N#ZhDok^hjw<&HMYLFPih#D$>uI zh;fVFE-!UaN(S{3N;!fXP(%-Vckd=TFkTQHnA2-Ne!c+AI7_f45kWbL!vx;Q zXt4;}OJnbu1TVDcGDqNpIN6P#5-S#6zOh&P11t_e=sWY;Qzfa^*D4ktSyJWw+CidR zHxnmG`+evvK-W_MMnuTjUW2uQXD0HYzv|6*>jgvPBAf9kJ^sS(t*;MNc?7h-*= zuQc1PjJWO!gptYxVgDSN#3W4|$Wgfd7#EadzgA?@fQqq@vvi`w_|GJPAm2i5OClq1;JHePTL^ed*we zlWZLl*0^33-Ki@)ZEcr`^o1IXIZ$BpF>_!*y(kYNwK@{k`ujjd^M(oO&cuX~>82-% zW@9uZF3%a`jy7ngg*V(wYa;o1>VW=lOp=%rqZgcKSvL`yg}jdNiN0Ye#t7sf{*F|* z4eeOD*iB-tOc8=Z9U4EbCpq>y+j+agJmmXblu0&{u#%T>vyw8g&{O0(odQkZL|-V# zptF1EX5;Y@u^VL$&Ni6hlQgGssDzu8XLx*!KZS#0Xl^_8b)da8{Ij6)K?#*G<;FoL zi=&z5rT%ng*3gUFW~$I|z2^h3&c!%NM2_p1L~hYX`V(9+`q!~EBMbgwZOQ6wb5*QV zMh!JbCDI(C+>>3I?CRuru9s|H1niC0_-*F?l*xCPyn`f`zY_OxGYZ0dqo$&GnjQfh zMvF4iuTkJuf()$zQpY-2$y{MHY2I1awM1irVH`9c|6`t#S@;$;`IXcG_d$v{$Du-U z?B@9n9d5CV+43;lnMLS|ep^LL4u>pOiZhF~T5;QtUVJ^?>-?h^-xz0oSRM!tou4wX zp_L-4a3c>Np-SQ&-NsX|$Dw3Sptd1VceYD~Bp-O^oZ8*Oc>EkYchLxz5oIcOs1xpkX)7<9Ct@RU$gC{XLnu`g&wZzUPZWYKgks>&Gsdym zJJL+wvUB$XdwG`LUtn-e=avWb|3sz>24KbFbC4je?YM!dJ7!E>1x1veAi@AwfyHGf z3hXimL!@6=zVC%)eCXEOK5`$o!u}7mfKUPHC$ww+IfVa(qFx+*3uSwWBm68JgY{1H zW}tn-yvAYlF?L0I9n!yzH^xUa&Wv4g`rcpiOJWmTU{2K4|IM6eN5r%_Bk*bF9%dp& zf;jmH3_=J4>1$5D^F>W6kPaQgbX#5#n#H}2gt|yS=Id6Wg5#S6pO;4)+D<(f+ zB0~F7=AL76gvn2sRG2)@guW#ctNFXk{XUapNWwumJI?%Z1MRVRhRHmW1tyxwB9rHt zyuhTzWSPk)n7qv7(@Z|YkeZOte`MJYnfzxaf6e5-GWijcA2az|CSsXRF-Mb0 zzs-b_QA5!+p-?)-+y<7l6EU)Oq3j;~d>$g0eGoyspdlWlHiDGbyab}=ozYzmy&Eh4 z|77@JHkHlbm&y)jbJ=wCe{D83oJLs|Wvj9y*+HyzW}{yo*FwiV%UL#pUoI+->$5z5 zFM~XP@%?pCei&D_haSqa6t0*Bd0zScW`u67bsX!R!bbFFQ+8u^L-xAixaHC8w(J^| zUzaUr*Jf9X3fiRz#+*#J`4fOv3l=;rL~{-pRtC0E!E0GCMMh-31ssih4TgvF8q_`f z*$3+kpK$NxN99dMzJaeJIyU0vaO@x+_A6+;l-hOYQ!-P#_^E?O?1|6c7!vT)O$_Mb zfiDc%uV=_p&-ZRYz}^EKHMszO9+0(b$|msBZ{iZ#)4c>i{`o9eP89Un$#45`PJ}Jt zRCS5T!5^E^N@rt|!^+J+qAC3$Cb@3%6Url-eS*Ag1U2YuGl$-6f>3pBVZv`A2(DYC z;JzIRu^Tr{Irf}@WvJq1{4J4VP}zgj(%n%EUGxM2%FxhF?A$J#IR#*+CiNM#fIp&v zE!t2bhtPPKG2+l14O=)-uX5wI#1(>fUPAR=aQzO-6h}b?!a~RzUGkcxUAXJ&IN1qK zKZich0tMl?PzT*d>|@e-oMk5gfINy34jl8etMlne$lfrGKP7YbJu(dGel!YmMkf9w z6L>Nb^jw%)&&>aG^rF_;^L6YwOQ-Sc`bWwW+FyeV&zZlB{$Z!NT$8OXz&OVZcF#Yu zg9Ah98)thRFq1wA`d6c80Y8tQtZitPtoSquUOqjFjQOqfth;O0^WE_9wD_ZpE9CLXloK*OOVX<4; zY!NQtNhvEj9CS>hX^8UhdA@R#$xbGlm|V|l{Pq8Uiwj7}646z?cr@Cw3=g507>lX=Zd zXQhvN#J90AJsv)yNB&=*e>~SC$h))k@b3DJ|HJ!rwjR>Gm!%)tJ#4>~-Anjg&~~`} z_IUX49xv#9>#^Ot+wT$n@8%xf`(gHe&b1RJs;=*A+331J;L1C@2M%oc;ezbD)_H}Yy0VqE`wa?~QdPEl| z6)mf3S?1&p`>r(*Sli*hs+Pu`gTnW1vyV3Z*5+c9MbqT`1~~Rk(MZp zdIf*{$Cv`(*nUhLXmOH7L zl@-zRoW_p^IquD+Q8!cTe(tzioS`?DCC_r)J)CrKOL>=obNli@MIhi*uS`yK+*fmv zf4~{X-QhI1B=^ciF4Y_DoM^RVkBYk^N*#Bl0w~9QTGw$8i&tqaAl<YWqE?ZU; zp7)B_F{mX|5}vm*4w4}aJA1w~h{Epc(Zw3^(eQ{(weE^;uhHmC%Xfw?m>#h{+qb6S zpJ8D*r6>910{M1&pYqH@0I5-f;?y*xbDbXlja2uqj=R&j{hJ1=3E#1Szr08tOzq>o z?zDc`y+-LQIVcBBAvWr6Tr zw+n(M&F`Xz)(`h`-1QBsw`G=i4Jz~51JNF5@<`r0tzmV8U&>vdx!1s_!Hd^HU?dhn zWN!Ad|J3mQX~Mt8FBzWqdgi=5Q#|}dBdX@yvFDv19TPn-DyB?!+~7I?fySzrom6e* zc*h<1`rcgvRofkRcv<-T7WH;bJz~eN6L;T@yI=l0Jg2sDbbWJ6xT!16Lkh#q-_gvN zyD}H)vq%k>l!ouVQ*BQVtnP(0H|{8zbF%w(_+a33v#)a8lA~j^$SsSz!^?A#YZe=# z1OuhEZ;g{%?=&tebsE2^n7KjlgX8JobFISsMjR>!#MAN08kLtAW_sQh2q1H13-;}e z{LFurYTKwYAbCK@O$NtQj&{Funm2}<9uig6k>{z=ebrrF6TW{1uZtHV6Zg0B^aFcp z>9w-g`eZN1U7d>z7wC@0TrPP6b$4^)H@W1HTx1!AMAE{}fqZLl|z+)GHnVM_jsA(rT z5Ry;uNN!fYi^^JoxWnq!!8jTfSo}N5!L?WmOlsSQS2QH4YhBLd2&oKvK+g(jAV6+i zh3UJvdr4WXyCK^8Vb^Hmdp&B?Gs>T=Nc4HKrq*3k7p`3&@3t;m5_jvJx~i7?=C@|o zL>s^ElI$@eJa3=U?4G=I)yoU}M6)F$+>*2VcZXe|_aeXfG0fZV8t?KQ9%YfG%RZ;7%jJxNTMO)k8-@85F zU9Tr=V&U3%oNn)AOC0wyk8vAk$D@rqVO$i(m5XsZVcc*SHzHdy+})>9j63K17$+tU z@|ZZNzIy4*=bXl4jeJP^3pQz0OPuKw9pg`_qor-5mtLl+H1h9oo;m$#Tc+vO0&{vS z?j2$?hzy(PtHzq8o%q3xC#wahPv&&Ob9}%2Xx;_ ze5HIr(^Wa?;3utK(g11Q6(}17Es%>W#NS$w04*wS+A2YYUZZey{#vp#KfOo2n+-ee zh3JgenQJnK8W%3s9C<_KM42_y=bJ-N9>+ z42Z3S*n0QX1+UYD==|3K&b)g!<0Eb8nOb)^jYoD#c1N$$(yPOhg2&S`=HnTC)IH*z z*yO$`g~{)$NP=T?`;c6j<8Gt6aI-WPRgR;|cJt7P982}BY z{5af+U}pX^)g#mA<}6J`>Ch&Wk+}Jxaq|sIX6{SF&C}q%EYYhGM5pl>8~hmKZ{obd zmm06|&-}ulv==^4h0Q#2Jwo#YJG_h7Jkfgh*b}E0Xox`9UPOA( z9;f-^L|L(_`TWmH+@1&*HT-ST7DW1xkq-R|0f^4uOk?0&=E@&2Lig4j&787MbpKpP z-M3|ijdNU2#u@s;SJ0w&s}6UWX7kG~wQ1b+m1m-k^>tN6UDe03L8~M85IkU1VTdM= zVDvnw8q$#{gxF;DB-smo40X}@TOq6M?Vva}yiD3d z5)5d9gTAQz;!Re{*_cqn-`yd8yxT#50r*G*}Ap6e+Ij3`3sb(UN5EFy_Vf|4by@J%5Q@r@UVNCX>~Z6LluRv{kojUSI7YgM+NvI@b7 zZ@gecxalr*R^zg#n%@e~oyj9WI|`_G6i~N#sCN`l?N%V$@IofT2L0&82(6O_yD-0LKTUDULkKb{>>*|8Ep4JX7eqSajeW| zD)TQAQI<$?CY^+0ahX|9wrb;!%V}}Tl%Do{b(Dw@LqB;;G+jp&VZAe9L*~MKku~7j zeG#AVZ9Wkw`#|Pg5%CKn;wuqxITc1WOu3D|YItn8&jPAPBtv9?C5c7|}GQq9Xj076gjuT1(V@O^ka~#e4ZL zdwX1*+W>gH8tWvS{;eLFF($ShNnm2ZokrZ3B(}ZGp9Qzr`xkmgy}_~2UC;27j*ONS zCF}qE-vjoBY0Q5KObrEWya#q<9&C`nUN3-Uug#2vhYQ{@{NGY0dp@O(@=E>N94L@~ z&0N0VNh|ffV5*{xbIJoUl98pwb`;SGyS70gj)iB;utkuiUu`I_Gxmsm+l zG7@)(J&X62zBCwJxCutJ9Sfg^PAyOLBi6H+o^o>ZVemOUxuI8(k+_@59!S!QJX>by z89lkddx43&ZS~C`CrhIXH-juYe(2P`*bO<-2j5I~wx^Tp!t*Rj)Z+jE_gBFUanffH z-FDmMvi>!HCbtqJnj71l|O_t-1!NhcjeYObyhj|o>>nO9h2*O-t z__0u8o--7ak3HI}G~DE((A#b`g>Rf#TC^d0H4gP3fVXVWM5%OtJ`?3 zk()3X)^#NL7uISSJWiCjg;O1T2|JY@P#=yDH)+tnsDYo0JWYnrAa&|ncuMJUa51ze zjlB?0FP5tV;7!cIcxob1v@V9*n5_a&=L_tm0ElFq2imiT<{}Z4fb|B2K(Rh^@s;|e5g&(}eoESl^eW8w1$J=}GhRjY zq{oG#3(rv7ehkBib(b2f(MUAhBp_(@LUn3X1r0e%SWNuu^pqMkAQu^7m@R4{t|juA z&lBd8gc;^Ksc98XuDg?(JfQ8HZ*w^S&j9eH5X40n#ss>OWOy>uSYiB2@o4?c3B)$Y9b~wSf5Ui;rv^XH55p`GjODtEeVfE<3G-ik z_Vt?}Y1#%%xT%FSpM94;p$crU^{o0hx^OxaXNMWjkPD9|1)lX+C;2>6n{S0n{S1N0 z*2U3k=ysD~qA@U8SM zQ^>CoGK)2)BfpWnX=+;t%!1WoLpZbfXwlq~JZ!-lYglL!RWheM2yk>hNr6oB9Gh9B z!JWndrj-bHuLlYJwBtAYz%a|jk4E@*UGEu9Ea2AvXfDe*?%;Y##)%5xoyg;H z)XThxx`NYJ*f`x(2))#(yaFUt5y(7uh0JI|FTM7HM>D-;90j`K3d&-)y_)&4XtBAL z9%cSXXabvQVUf{VPD0{Y2Mm3#rlvy^{s=TmPE~&()s_y9^OF=8AEReKLL&llkppP< zf_!BH?NY_29Mtt>?n}0wd-2fs_;=B52q zWu2SFHKBI&GBmI6Y+C&QQ>T5?^Vx%(r|p1!-S`Lb)qMB6SdP7U>sGp zy{4=r(c6Q)<7!}8nTo_f2L_dYn_lHgc)Em|7;^iqEi|5{A; zs4hQVKSo;x;+3Uii6pa7>qWuRH&njs-2Tx@SW;K@4^&svBe25#o5Yii79Yh^S3MJT zzu@Nu{@umDIdU8Z@bCox-sImq7BjmNM$x$;8;+>A1bCJl0m0EFcR^TN_x;oyW+e-k z`8g@&e6%k)bVD!GdrRAfmczhR=ohgpRX0a@WlN4UR8OhEOB9GRZ>8#!vQ z*T$iNWY1XRcO~KU+&zhE%)N5(tpw=espJE>$VY;5BftLzKdCzOtNaM0B$lqKVcc&BLOkz9D`bu!!@StTRcEd=l*LzGJ_1FjE>G}_( z=hOAK<|4m_zw}tRxjv{~8l1q{S#qlJ`?7sRO1*S-pD&D@IgoS@a~;bQs}og(j>AoD zWda&rS{Se;i5{7E;72x?88TBt3*=2_(@w`tt{_6pxYK+Qb>C$FMrQCW%9yoYOZgSd zJX(#w3>bS)X3gbZ{O(>WTK9nXT|4)xTb&hk4?rG5Qc~j_y+IO%IL&&P>9naoux6L6 z%=~VmH$qZ%Pk5s;+~m1+v!1DQUyiyj*IVTFtP1C~o$=~b+L}~9^sEYgYGG;wO@$g^ zXHwslv2?UcEH$bu8}U|va*^?Lj@R94eEYCo2@$NbFp^+d_96XjgJYlio#?pxJI!yY z`~KRg`~DesFQ@yiEmJ<-)~&wz9|V(gC6#gf=Rk=E8kmJnI}Kxfmrgqh^-8BTN@ZK8O*5$Er(UNWh-&sb ztsy~;>H2r%ai;6uq`T&1`i}Ry>j*1UH%Zn{@PnVL_E`^vn?54G6L;$vF)QiF6`9aA zqQF*{nwDAKtY?sij-BGJZ||1Y+U8bui?lo4azcl0*(6;bSp~0c|0|~6tA0( zD_TZrQK?QV9KRz?gE3s>BRwcMxBi}dZFB9N)h|@L6k|SpvY%d2F9nY_&u5%ZM$A|K z3l0bvUO3{z%TA%06MJ%#1|UmN0oC>rvEO8h5ggIweuOY9UPxlLHa zonl(Avcw`56=ynrW;>sXbB`joomqEzeOb&e>sQD6b@zAkA0W zjnTP%WJN=wyB5T=CH$?CdT9Ct_29U{hiQG|kbo2qx^!sW2jS)s)`hdKVan4K%x((c zf6|KjBiIvq|0A)Boq!io9~pB`0K9QW7ohQBr24y|Y;Y!sOkQ;l12gVU8bn3+K@U;6 z*wDJtWF2df<%*V_Qt#eahOK9zD&Oq>Ie@mahtaz(^S22gL(ZhEvF7$SM&1fYwhvTf zW?iVdc93fWgPj9SFZZINqA1|$hHq3EaXNH_WWaWvw%CY4;j`(qe&{G^_~s4LBt1M$ za!N}qT~ko-W;I6UfXXz*fi#qpCITnlRIWu)ap`NyCO43jp18;*;RW%<@;*#@h)+^Z;wG=m* z4*o%;3<-pryOZxU&ia6^`KE^$6ncv+i~LAlmegC(DcHd_)2W_N@#c=dgvc_MgRhrLp* z(bf$6q_+0h-de%ZY-`PeLA1|bk$lbRwm~bBqn$XLFJf3o&8ub)h$6hn?krN5Wy3so z<@tr)!?WGo-gh+aJUVfSlWSpr;LOd`uTjU~Q~T*wRPQMK{oWabuX<&^ht0xj-jEy> zb2nH|?n9%jCok?!cU)oJ@k90G9SD&{z_U+)=BnMINgyb&M1oRMFbLZA0#hFS^Dw~B ze~Wp$f zQRV;LcnCM`1@%$|UR!_AJ}CN-A%^=(4^BPw0Yang_MLg2i0p=0*+`yKE*!AYJAxr2 zDo-z>@?Z@kH*^}k!1_|=(=q(YqVz4eZ49kqrLyG5f*Z+%%F06IwSTF(W{nWpWV?QD zLzaCZf1+zp>3K#xtl`!d(_X}s}sL~{}%dIw>9Klr6KARLak;JTs%_e6; zVD>^kHuf166~)G;oI{>sWBqBM9~=7wYXH)AiH%)-F0hJ?4TP(HY;5USqMPyFaZAh^ zPR(L7 zjc9i<*$$`8q#Sm@ipJ*Dnpk=IJq1V{`-CrN!jQJ}c?P_nzxdjxk#FxQ!iG4AWCQ;+k=ORiV?V>cZF97odUwecD{g930S?S!!J`Hact2dKK{vy z)vb`oy2yy8uC|S=lI0=?d$lF#>c|X=n|HWZCUAqV~^1X!Y7m(eB1?IZaIo+N^6ZFj1F-EG+mmz-|--;$p_)qnX$FUNtbtB|E5y-dx4 zbFUxK4PJqrg6XxyvC{AHqJtK7a|pkDDOOv|q8`6{T|J}CeLZs-Dhq&nv}86wG06&G z=E#r0(UZd`$B)*n&kP2#?QBS0QftVcR!ztIFHh>_Fi5)wzChY!RK9iSt!|;f&>Q?)EM$v!l$L3yM#}Yv7Lt{XIW;GY^p^J?j>j2`y+b)z248U_l0`@ zMf>}Ude^SiC1WiAQ@zh>fB#7DH|qUd%m0hsFX5f~AAq&AXQho)$avz11+a|*J5j~7 zh8tZ{2F-1Ux4-w%`@VX=#PZLSuIt|Z9@qPJvZ!W|SIy9TH8rZnD|WiT)_Y)kcwkrL z!S)x}<%WHxz@Fr>NV|TmwEIb;Bk!{Gc&ZMq+iyS8wR6kTuh)a6AEk6zGM&-~C_SD} zm-hDH_fz@_md`t-1=-mN+!fdORfbZx};6ibm!53|CipkBk=zFn|l9O z`}<$?zN!6vqu!V6eWKx8rT2NfBT)}X%{JX(FJ@A&n9Iyu31>IKB1n6OVP`b`i;N{V z8-nW~KRZLwJ}ebX?##w9YPu~!hkTeD1arQp4l;MF$mP%m$$aKb$~?JPEmY8AD*fzj zw1xX-E)w9~pAMU~sIX+J)!aZqZwy@Kg@y=i7e#sg3XQw$>v)C4&LNLKD$OMZ*J-Z) znV0@%=k(jX^mjU^Uu(EKS{-9a^Wt;TpG43I(Pg%dS9{Qq7cRNUT5=98X*&*$>%WiI zdw-h@LKle{>Bw_=)pz|}FyI<1wn%;VH7b6I#0S~foA(cSkGoqHP%e&2tfd%@BoNfm zAbHM9zpHcl!=%fWxc%jo$b%i>u^n0$UE*!g+GT&#`L;i5h?aE6*}kYm<@lq%AyU*~1#lps>B9xAnvXG+MI)fs2_OE@HP*IGVnNZBpyR zmIqOu{??>Hg7%hl`lKfVuwDRC2Hy4rjN6eIrP;XqZ_wLIYD5LU1H4!E8;yXpk*s?u z7i(#RwjaIL%yQ93qruHY3*zq4g&zpZP9G%eKpCBYFB87;QLy~Zmj6h8WSz}7wXr5MqQ+Wi-Z_gz`JF9IO{zdv zns%6-NvpGG!1JQLKp4fT5#@+O>McOEuSI3 zhp39JE|)w2&&^A*Q0OKBvctAX1Ej4YFBz$E724S3q$Un%yP8BR9bFh#Uw96C=wcD! z@2`S4wm+yrO!B$txWjH1^Rz#xVvEnk{dg#F@njPULSn%(!{zO-5{lHY6AhD=igG=) zKWLtMTwHv{=i;}g3KK0PkcM*0bMe$hB^wv_qP3HL%J!tH95p zs?6gjahrH*DjfV0Eid*Bw&HhD9CQ_+D-y$>sWQ|E3-S1HY?sEAYbGswFx!6UC{_%| zRb-D@9Hz$T!rMF?fBT6Po(?`8&zw=~F2@wle0mWkRBo5;Mngch%f39+hF17#3~FE( z{T(z0tY8bzkmx1(wSO(h7VR(kF>SN`MWuSs{-XU516IvzO&PNjBps=f7LrADF)$0( zh<)KqIktwFa@c~k)|gP3#?X=nQNjNHqQlgZ7H!Dd$0WLa|L#|FS*BFWwC5dOBTb8_u(9gY||1GI00?mRD`VB2O8}KArfZ_5kv9r zVIZY5rqTndSz8wDpfJ61{!+4}^fP}SCKqFR&jknU&zL;g8tfl~uW9>6HM*gwYDfnc z$ed$8)S#{;vs2<%diSsf`>31Kkw<)xif&etZojHQo0f!oy@%|J+YQ-`d1TZmWMhkv zwMx~cBj@abtX#;-gba#MKe=v+)0u0vZ6hLJH}q4m)8;wC0BJh*)=N3*;D2Q1iCU!5 zc6EuwT;!EGpga=lfo3{nK_z9_K`~aY zLppRg6-FgY?57A=K zYlAW+lkV$(w) zOHb>*=w?xKo1qCE47tXpe_5-|e1PQci>3#@w6A zrGbaV+)D}0G5ZnbX`d;72cRv@a~xw$=93r|%27uXXsw4$+%5oFR;P&Hy_&hZMg(zm zv2fJx2C|tfl3|6`0*jiAmlCk{fK5ywFtPO%B^Sbi$6NB?|HilQ%BEDdfv%>b>a69F zdDfLtR&{p?Q}*g82p5AUJ*~&0hlIpi6be2%6I??_lqN?x?o^ntJwbSJMi~v&USang zon-JAy?z5|y*9UP5~UwFqEw4s`4z8b^0ngirnofsG|`9v<&p<4>7iynMXmJVn=;mA zb}S&V?eUIm7%pn62VOrz4F4c8g&2W$#lFRRVlYj<4t}639$@tNdmL?E(PZ@uFrc3X z`Xzg)zEmMr3XX*5 zMc7s<`y>6r^mRjJg?}kP4bpoVP4Z_p7{71#ebmVkXGjQi@%N~3o zF^-pdt7Us1Anwyhr}4J3z%A@RV}``ws?v6-Y7;wiQ%kCH*fe4yJJenh?Rn9s2%>+l z5Yx#Tp8r8@r!VWhC?eda3wL^qLZRz3*X?OjmiA-53DBbbfG|4R_5mzhM#R)k^0!OA zwWqq@<n(3 zI!J)OGc~wAbKHd}nv7-&Hm=6SG-e8ssd&kviUOH83cj7XpHq7Zol)JdWtvam`rCj= zM>gA#gTQyl08fTF%p>qH)q8>nL?@B8Ci80zp>_fn0V)EI5MVE#zP>M)lqYo(0rx1a|94U|*lW zK?1x^73TZ*huFE8Bx2lGn6`_KFVJ-YVEO*7DB|Ea)n482_FH}S-K+jgM~0f@!M;by z0NYbwvl9x7PF1B}n&vx$oqan!kxP-EzM#*d8s)x8t_;wfB`lQB#wdR^^T$!JZ>9QI zB5;wQ$Msn>O3+UkG@Ai*BrV*u!BFEExLWu5**a;UT@>m9sPtV_;p^q&J4g5~0D)a8 zVhA2wvs(ny)l9F;nIw>3@!}!@qTk31-X|Y=32Cz@H5s0JeQn+%lY!o@UtWPl~qyV3VR0!1ST2U~|!=c)sdt zek;idj={??MLn{wgBtl&tP2%nd7#xKb^8#_^>#?SIJbr-^RCH+-WH3lp6 zBNthRy0PV2&Q~&bFi7`KL+#esWf$$rHgC^$^iMwfGsXTt_D`A6>pPu zM(xB_uJNR+*gtur7_P;K>+GKlDF%AT2O9l7{>d3A%Fh1DSI;94=AR5Z&XhsV1+Pf7 zcJH4&rW(@00~+`#@=vdM+|747ZY@H9O9k=pN zp4YpN>}MXbKkb5SnvlIDWOq=igMX3~up8P=H(+y|FAQLle{#KY(!q=K6?F7Z4in11 zfbx6&lU`?|mGb_{CQ)zx$$HA*pFFSNNIKM=3Vr`%%^ClrfASHw$N$Uz$sQuA-S9pH zlH{N45TktmWH}OJhWEOg)!*Uo@lPgAA-AJ{@-Rt7$X{s^^5CNG2ZG%9PtIO$$lLvs z8YP&2a`+1Tlc6Q%Ov%69TkzJxARd3Q?6KUq$ZUHub>$`|#a^#AOiJO!>I z|K#Vq%0GFJt}bfHH;rmZ_eFh0jqjfv>$M~o8ELTX{>ecC>g1mcS2cP6B&e7FjepV` zz;;!9<}hf=8)=?@lAY|UV)-YZREg$d|70!MMgGaL#!E&+kv4rwATY82ASD-c?tfYG z;BK_r^G{At*}Q*3N5TJnjRCv$Ppcs0w_E?@c|hc!+$exz|D;kVI`}7FGafxIos7CIZ9abvhFz~IU)8PlIy`Phh&Lx{};G9lU_Y+7KI3+ zyi&!vwqbJt_s_Kr`{D*Y>s;G9-J3^9w$AOT4*=Jq_dw)zyTYn8pghmq zMnR=!%P)Kqq}G5J)Tnf1yh$((SV0C2*nlbPIX0h(u3a3PXUUgi^Ka$u#e)Ry! z_c}JC#g6KJ)35c}W&N9u{5a392g!h4kAcYZ5PFNG4vx(w=35jTn_B=C?R^Azx5;a8 zfw2599h=3#w$G-46DirzvH1l61;^&mABetU$7Z(b@*SHGXXMH52Q{4?n=w|C^z`Y- zndjJSjqc*u3>3n4$L1ity1`Ks`H^Fzlh^n%qb-D_i~H6(?#mpOAYwGSEINk8pV7L4 zOdDfT6FKlOIl~gwR_>8yA$UVwI#J1ifoUtXs5~tfIT8tGnfJqMK#sACmt>;ixOjsc z|A|#^)-HUO>)1^|c(*mn@te}?IwF`!k2L$U`El!O z!$48a48hw^PJis&dJ_A zRay{BDcr1>kIg1mlGk=PZ`zYIxvC}R){oE-t9LW^FC($@-q2@AvrSc_IDh^TTQYx1J>jM& zRZVp;O0It)EkT0(9?gGWr54-la7lXdnCPO_8jFXS?&Y{@$5SQMgwbiBQu4tqThh`D{gP~d}SWPg1$M}T{? z41wzuRhXve?6eMDHk0Oa%do@UWg<8IwklxKyppjUp11jwT(TGOLD(qw1(e+P5}n(( zd)t+?h!#9gtYNxq#sR~XZsDG@!%6&xf$stPUtk+UYUDGNca=iK0CN}P zcXFm*mxi>v1atD$g?_&PD$~JFm>EPAli))Por>G`<;~W+zq(x# zsw?%AJ*zh6eypHCc0crH5BO-ip(7FK%iZhkX;`VQ2+~Mar`d<|B1CbaI||N{Iz)%o zuB|y2rCQ;*QAXGncdjbh-r6N`l+!qWF4bU>g%`ZrXDS;}h?y^;f#CLDPKHKa*z2Ps z<7=d{*kMc;e$>t5U4)Bk62-slfjTw#w|;*01}Ge)qNmpFjO zKp}Da-PdM%!6&7V(;**$I(#y-x9 z5#{QXyuIYzkHBRNsCQ)!D9$`ll#@vic6A1w#0AP6{iVI1 z3mXUbHsm%zFS~|3{IG{XZAa#5zwThCXD)McTL~vKD2me5LwSA#myH*RTMV^rmkRz- zDR7Qjdgohtqqyk?e`>(g$4)9<@7fLI2q0|-MMd#-&>96V{z`ZcJDp}z|JbM+MswP7 zksiiirz{s4sG&r>1JF8ll<0u@K+UyID){+h8pLmgf3j3pFjrCigYyAqs2?Y45&P(6 zmvEEQh6KB3Lu15T_JP=Rjmf$Xq#9Ws6#A})>^qgsE(^!u&J_ZG}H38Cwui-cAVn;6%vdmIWbiT-uqC8K8lX@Z71WqwGPjbd_LK!!)ZQ+;jmsxyWi& zoQ}-N*YYR5dy|V_%eZ_klXj^klCLF{uVq5Mmc9I1{>|){i(Ki|GB{sLyuFqo%;Ddn zc@z73jp~xGE&!@pMoqij`8X+c&AI7^w*5kLsvG)}a@M9_lcU~lw%WEE5i8o}^5!Yd zn)A8#VRTnP2cLYH9|h8zY*Pv;edZ}+r@DS18wZm;xOAh-{z?no=XJuv6ZHo3Tx#?# zecy^6(Y3r2g*ElN%E|WoT;()=bCCln7qx4n6gb`^z?6~>-f4l2%})A9m2*kquDq_f z$ZJ5>RjrDro~v{yTD38rT3A`blkWZ%7_u5adKnkzm6ST^fj@+j#a&3|!qhW|bV**| zYRMIS5!AHe=|{KZAEwF7ityr-G00sUp^y*fv44j)_-xccg=!v`zfqqRZ5 z-E{ZjKIIKxACv5!bCCfa*lX=bZxYyRfZ+%INu|?~Mzgm< zSL#i;|4qwS?u6FK0{*pdHV&(NBp_q>hx~QY{SM|Wd~gFV*nA`Q@2TyZy%NVY%s3#B zD9uGC((Ekrz`5A3eBmUwn!Rm_UWj5Ya*47c=yPhxVmF-2O*32{c%CSA&)8v6@Da>s zrOYzi8$splpAoO+S2gWv#4IDIGGj*|+4Lf^O#V=>FIIcaWVg^h>=v4J{w)lFgS@O` z{|E++;tk4r?Y+O3i?M_UIoVKf$+Qf-87pb}M^EKcOvP z$VHBZKCd5ct)L(JQUp8jLP2Q$kuc3rMmI7Xe-Fc_$|mm+aNZ|6{Pgezd0ewpWW(Od z3jNU_hW+XHdc#E6Z5aF47b(|j`~hYDt{RvhnV9V^C=}OTAa_#z8o?G_e24H)_VAzW z;lDzSOZA&zZ{JtVu#VN-;#G5SQ8m9EXK2*ZaO$GOb%>^@gJvo+gz8ayjswrnquxRGP6t7IQ| z`Rnuf+xE2lFrF1;w~!onm*yh-8P9@S^I)q0V-c=`cbh>FlinMw-2%@?`zY zgWt@19==j}ZshM!!aeIA{s;5;uQf7)4K(q^V8H5H$TQUpG|D5tropL1j#i6RNQBiH(kgYGc}y2ps_G2AT6 ztZmn7tSTa1jn74Xe4cOwKhIa24XeIj&$0ZZ`@Q~v<=$_Uv*Ary-ud9*IF+!3x5pUJ zT>`R%bzZ{13J?pu1nWP8_^p?aQ1dO}=U&28A4fVEQ{hzbHbJC=YaU}LrGh6YF&*sp zq`e)(8~dxEl=p1*)Ei@as7ILyOv zibv~11zOu^5c|@6TKoC5p6k;ZGFn3ko38pvZkSJ0i793OwvY6{@yb`&FUGi-7&5=+5Z`R>4F>u;H*!N*0Cp~Wfy!~j8n;Fv2|%kb&5DQjaK<=yvo(jA4pL1lD?;B;#(VXzZ3TXeb zw`E{g9SE48zmk04R``}je>&rY&mPgmKU1kCD#d-uu!vQE6KmTzJVyyym=6xqlf>Q9 zkF|7#NJ1YyPS@TA5`V?s!}^%8?(9XG0+A!E=Ex5ysulZv`l~LRSG^*?R*`R1#EtX- z$dluBJh84qS!{u_#WMG!iys&0LmjaH^K2{l(Cx4R%d0W7o z3spiYv_Wr24oa3;jO!2s4!vX`!P?P+jQm|V`OZd2{yV1SUkPi`R+d zuY0OQ=p|YQ3D4>0$m5bB?w>ML!CxuG4K>HD@PW93R3!;SUw!nuhqQ>LC z?dZeN#cv7yLmv9IA}< zm01Lp6b2^TwA|8yKg?qp8d?G0jj--Y^9h5DuShq-&eH>g4XalVdH1ej8+sQ8!Eigp02 zzATF_{#eXjTv{w}rSz4eK83OLl@*HA{90u}a*e&`2NOs>6OzFoY1bahfQfU}1FNHR z!I!9JW$vAN9yn+b`h*nJ(<3NOe|5C4=}^quD#|S`3uxSZA*_cAs~hSo7Nq(`^y7)y z6-GqAZpNEn6R45jkHwej%@VT>5;KBBQ(&XSjK^mL#AM3}sG4zaY*n}+4hOgN;h%>r*K>(G_|j43DdiHNBV_27+5ufzl$vKmDDDKhtQ z5~_05-bXbH!{KRwB|Djbz=IcAdu(7g@MugpSZVD8yNp+3!b!w|Y+xV51JxT`e!4i` zxUPX+nn#%m4b)?*->ZTz8rXjj!^DIy0z$Mt^QASMmNMG5TH2b-YlkV1u&pQ5x8=(` ztNcQ9-f{%BqVa>gP5g{Qz{+$BxN2ai)s$>m`H2Ih6eNp@S#d^QI9sVr6pRIR@2R^0u z*97mq-=+8C$VVM-{Dsu9oSBuslsaBY!!QE(f|yB0%r6D}5QWP5LdkX zqLBr#=>l^B^NS^T&z|hROi||BhHiw?@h?L;>H*iFJgzXfqKl(sFD3{7?pSR8fnA7( zgqxqX>M=6z0H}=?c`(t%rwCiQ6^JfASuZmd0|KH!yimSXQH}OBhT&vtb-sW0LLcrJ z6&mRq7ugCc^dkz{W?b$br_vCGvPbzKZGdF=MQZem+1sSw?$O0lyrO4YeZzi++~{7Z z#$oq}+5}(Cz{d-{#Ds5a&6_gg4pLR2n*r2fUbPnUaI7v-LHq~ z^43VI)@JrN0z9|BtVNW|yz|rk(&kPo?JXj|H#*r}Bza!c0RW!{x9Hc5tT`R#LrfAz zB%`SCG3Sm8-u`-O$L~SpKfhd&i+mxJ+-~WP=j`_6@$3kih@YAoF^+%Z`8ToXO5n~{ zI-fC)bBE(OayT)bn!pm`)ba7ujE*~C28z5+HZ!@;giHxCQ4yOT%)2r3;6vn(h3}mYc1e<*{fD@&>_b z-BJ6rSb_8pYmd_+vmN@oxZ{bxxcj$Q>Y~zmu6$@eg~II?_307iF%s+Do&1#5r_U(k z7diP{-$!gfELjnbr%zZKOP|Y{>rbFUv*741jM0NxG0LwO#oRu8_fOwroGjU9kN9ix zp*_P*n+(I~QZ5l^ZDC{b!x-^y&dL_<$L8{~GeD+7V~f_G5TaN$OA${}o+4$=&}dom z2);L!i~NzMyE{nA zmDAu5;j{PX^fDlq`HNy<;KHze`xsk!P@RIuv@l#*>=yUdiH`I-NJq z??$m*4#)x<5}s^O-TLSiekK)Qfuuth!;4(xCKHQ92?yR!H4VU;<(eD^JU8Q~n^eD* zM5~=LfYPIPr zlo$GYm{)1I=>^fuWDsuty`G3HnnnWm5W~#|i2l?}*H8cIE(sg|)g*3IBrJNG3fTUE zgzb*mV5r}N@!lbT&5CoeDzH_6SXF1r&&BeK<~Q1~CA|4YZ`qk&^yo=vf~ z@m}^nYb^T@GD@XBzdThByX+Ea*P*-BpZ0O@7$ceMax{Rq;OA4X7Nez>8Pzxh+lbGK z7*pSg3$Q_kV6Py2-Cn12XIC(!H3Hl|PWl#B37L`4WPPyrQf};Ys$ZG;FMTBio8iw; z9LQmI>={REj;U<^1R9DElzpS&eflM{v53?Xi8G38V1%UrFxGjs)}_fU1yLAE*yWUN|;x6|jM{9cvu`=sFT70(-F92+@6&^=Km>@yotp5=&tYs_7x zyTP4dDy}Mcd}@?I~O^3nDIInxdPhphT>_)c+9=1BIaJt zjmJ|5#oYI8xOddoyU4qCj(yE{YGCc;QbyWay0`HH?p6tH!3O63dpHkk_VTpIp!OX{ z3@KLs*5nwHjK;rtvZ~SeM|<=}<2uP(I`RiIzLCG=DI!*bM3(Sw=5TSL%-^$yZiun( zG4vn^llyz;%cS2Zye6}^me?4@eALa2ELN37|5%B3sX;Tz?err#zg7;b*0iux_g49( zzbq=v*rF(3?ip=Gb?3*qW$x)^ZfoP#^sol-Bu~3hjAc*B&i$#VTo5N*WL62*XHZ%Tx&8f;M90V ztf>a{&?|<5qQ*zcdL`Gc_2y&d+UxTNTN`*)hxGa4 ze8xDf6fkgz{3?RJ8ufbdl6By|d1GQP`@o4M+Yx*fN%6_)eI56{d%+k>^@+QD`sub( zzHL+Hq!>B1luQlh-C&28X+r|H8R&Y}nD$pbeljF8V;3LI{Vz-FxSw}6Y0NDiiLul% zj{ZMZv=(;6U8G{x(&E%QKNU_C!5&L$VeT9!3(A{7ZKu1ZV{QET_Hglc4jgwJO=Ele zZay5adFZaKdD+rBw&q!-bsALd^=*$rHMXn{ouk;sweE-tZjFh$E$ts*KAG+~G`hXT`vPR7yE5)~h^yNcbCdpi}hS0yv#iY}niUPad9 z5orAGdrlHV@0AcbZclYJrwUeT8#K3mQxz-L8?x-b9?r&?)60~9B>9O-$vN3L~TBfyHl7yrj(ni z*rI#d?pEYNTSKeM!%c7V9Aj$5KCZ7`J~K+uJzb%#(2X zdJ?{B%-bsAri%<4-JqAL%RrI)_n*wexpI6g_hgBbUMh*@o{q1fPU|G=?dvFzN2j+OZn4 zZx;y*k2U(bZ3PowBLNh%bc((iRL1uhcE2qsEr}GhDZ8^Q3ga2_kWu<8mL3 z9dofS5+(X`mcKMwaJTe1qS5TG=ZuR!Mnz-T4F(!ozyQPJaL*jaGv2@;$Gwz2A2xUK zA;r{4{lonpIkAH-eP7jfQsL{36qUq)Lbn&p!+FZkT}Zm8#;Txdui}pE$&aV}e(UWh z#TXVVwjj6B^kZMZV7ucuxm$Z@B=VY@-rMQ6<3-rS{s4!qpl?kNI^8yNfphg{Cw$gs zr}~{a#4ds(;J?j)#pGPJ%ssP0c7#3(V4tU7;D9JxyVU8nT!z<3N1xF;`W?P}+?DWM z<9j_?+qyd8XWndlw+G)1=-46<$;^@;uMJHFpR3C?^=&*_s=y z-QP&v4RV{!u1muD8RdNATD-u*93O33iS3=}fi~e5{fqc_zQ5z3@i1dz* z*KU7E^DxeKbD*=VN_AQ=`xq#X4bm&-#tt56kRpLMidpPa#GS}T0BXCXm(Lsr9jz7OZ}!nJA4Lr zrLZuhAQmhhpYwl34|~<#XjX7~RZ{!;V;&K|GM51&K0Bs>G!6yz)vcbl6m#c}(?G>y zImB_#E~Db+jk5_nx0c6;?vc1$j(TG&&eGDEpJoTf+!mW3y~&(!lO*;O(Vwy&Kbi&< z&w)v8Qo2a2Ohw6&TtG@82QO zXt9pG#ar2o7K8aKS-o`YxFMl6>N0hhHcNikz7wYXsZlEuYU{u6t500r6I|E zqUO~}LwP3MM=HoL9U@mG|LKjBOnfq+XHi7`#dyU3n{3IU%Ll5i;)(Srcpq+>3*@eo z>%YIW$yFcTp@wUAGoMyC?*A~K-ly6+&8KsW0u(^!k$$yh_%m0H&8o6Mb`>{IKlx+M z#y4G2Ze6j!?u-iTaorTp|Ck*+QZr&dxh7g?*?8B_f`j+~7Vt9=1Btk)jJ-62@IyWSSu^JuB2j<*kP2YQ#=J>Dg;^K`S$06QMQeNk}8 z#_0SE#N0JeqqqH%!dNw*w;v^PB71v;^F=2S=bvqKBM;9O@w>=u(&SFPr|9$wYRVEs ziri_ec{TGL-$sZ3w~;i}=kc0v#A5|_5@(QHYk1rFE55+RxeFA4;=9qsJRSgNjK^~y zwp{#!Uy#&hU;oe-=DkBpXNx9qG~bekx>ZmQ@}~2CasZf;wzwbCHNT;IQ7XwjT)dZ; z2V6q5Ogc!Wl%r$xqvP%bR+ti;h;yg1q-DmyjPaar98Wj1J#z$$koG%KznqJwD&PD$ zor_Va;^_ubhC1cR-Z(OgcxjUYqis89>1U;P=hqiU>V<%B@aKZps)_9uxq&x*K`8W% zaU$}v6)Nc4;*r#5{cMH{^EzrHGeDSzU0^JA!ga&0QG#7WXYun#^@_?nl8ZCqJabuM z^{}IXPMl!V#FM1aO-EFHtc^Gy@`lfUC_i$NBPSarp$X8%y|&iu;srpsg`B~%8RNZ^ z$|)G>!qE<1Oh2%7mf<~=M%kLq>Ui4`%AjZPV!Y1s-lJ%j%jpPAjjU zu)4n6s*%;Tt4e2=(zl3zsd8o?9d(yRk6!k(qnEz6Bie15GhwCEZB=b`?V8eA!CImf z-Dj8ArpI-utN!cEH;OW6cV%)nBzeW^|0?KQQHvFhevMa_o@)vg2^&)9Zk5x7E`5xy zMRCFCa?|ITX{Z^=aiX-=ZHa!hIoplzw0Y(>l13bxFA12a)d6z~H)h-_R-+OIxsfZ4 z>#t@?7@gI@^LU#TaUOKKwbTx+U0$j=yzlb1v$~2=#JlU=S>vj<*K;ocHeAfTk*f)k zTuqSVYJ%iMZGw3kQLNSHZi7=LJ?YR#I0(7O;=_f&L}`?X(r=W&Am7u(r^>|Va?-Ml zB+ku?e(fVYQBrJ1eSVGR6f5{v@_cRMBm}8@b!?^oP%=8Jxv&AFivEGFwVOJ^&0F;7 zu4`Lkzg}IOOEMYRoc*=#UEt1z@!Y*Iu|73xT)j2!#*V)73y1ASeg2BX&Z2iWX|)LH zWQZ;K>YZ%2s6LH2_^TNV1i1CJ1}@)eIQhjuiP-dx(aSvYt};@=^}wwNw(_@zgcZR* z^90#}lrC*3Z=27fJMdQ|+C5J41BYqGnXs+;m05?gj7v8Tok=fZ(f3~Nr1~z`PK9uD zBjs|D&nsc~3^#Z+<*PHvW6~3Az~D@H!)bkw^IhVn-(}~e21WsL2cE=V$Qq)b32(%z zH_c}3tae)8EpfWN>cwZmWzlUMNpsS%ZQ(tmEX{11^HF`b?YUP{19#pQ2nblFVsY=Kty4#` z{^|8oa&We9c%II|YgHs2xfhowyC=priG(FDCTXmBL7~})ikVp7|2!lRc-CJ>IbOCb zB{QXTzHeE-PqQ^y49c9V=~4@x^YYG76eKm_dANP#dqvS#Hu-dD%?(AZxxiX;hW+Kk z)*GiEit6765zw}W(FReY^}eEo3PqV*AB&n^OMPy%FYR$!w;ts5QbPwU)3+V;n|G5G zVakl`mTv*U+VluCr)*inr86MRa)tJ^J9{^)S_70JA*)ygI_bmRw66|7u8i1V2|L+J zZ~Xx+xn?v9u<4HSXF=F)sh!OCNoCKB|Kw4_3qhRyRI?GTJD+@4IJA4ZXZZb4@KG1^ z_sK4%`rgIQu(8Si_d9-n!@qe&Y06X6tUcAMlRev=FS(_Kpzt3U-|aEeaMLe&m!n(j z+T99h@Nfzx9ViM76M(qgb0k|5gnaza@a%mRQoUDk;o*gNEpcInThzb1!Sg3mUT}DI z;y@WL%IAi*Z|VCecc0}!J@>2d#yUO0;;y1iY_!0UB7o{?7LLjoo8h}#A=61;h<(cK z0Yr7z@a{**BF>7*BJFirrX-ZLJ50AT}%c#4a zd6;wk3Ls+{l_kzB#No|z<_LECdFY~Gcs_o-Nxn0hnGcL|$4d_JU&b!?cfvo?$LmZx z(JD0^8)8ViWP2>hzAM2(rW%nc`d6%|4xQK^_AwW`8^F!`yo2PSFXickgN?B*(Ow&R zZAkW@;L?gfpk3+<{`Vk>1+LUPg^+I-Jbo?cLCD1zGi>_6U3P26%Qm$e(^)!c5YL-8 zdJ}Pu6@Eo|^8O7UL#z(IE>fC)4_rH9*@lS|;_fQu?YEAVz=fJvZ(ofsmckR8-`~*_`+J5(W?sGeNPTqa?S$plZ*Is+=ea>EUCdah_4-BmK z6DO<__8JgkqUvGm!;#yr>c6d>0TK zpDp`icI+-iuHpLE)b)0{9?K%84}mHCn9I&{8;;%_%eRm|;Tg*W_Reto z`e9r`rTO?8zkG`yUIl7z$NZc#1|7cQTsdty8WO=E-vJX5dkOz{ zk)NaI1oHW5`OZJIo1-XyU+-sfRADxr;l3T_4Y&}y6U~&G(b|^05rELq8)E^qES5>_ zH$f9^g{P4((2OipPL+c#Fvac$b^FP4dz28%Nr+wat~2_n9eUpx zUUz2SVYe9X$j8P!JsEumk8^VuP9E?I@fnImc?(r^+=5?OGi|{nV{RoogiU7RA-D~F zEzaSWB*!4LeE@=EJ1<{*J1MYS-myK8cG=s%brDQUF{AX#Ef0Cm z^KH6E-CR)tj)2O}px;$>K9XxX5 zTikN^K8vy>ckF)21d#$AInD6R)S$D^ATRc!;%<(8Tau!KxShxN{Omp?)csZN6XsI# z_%D2y23T&@$?fKA$f zjctE0ZTV#Em*@oaWDx=d3n1lbT#ZdeSu~xb{xO}K<$0tSS}twK4VYh@N=@k-#&3n0 z9|i$z@i2f+332)@z;otLHB@7Mg!oiG@Y8-JMfZ z1(WNT{7anM$Gmv&LdR0rkh3QX4y0jWMSiq) zGk|H*O!H4F9qoKA_9Lja^fdI6we2TZSKdGQFgZb~N<)v|M2^_`S|okj>1cNB0t#S0 zeH&mjm~5=WT+xAX)mC_jsQLHU>>Wo7%JHBT?`on2O8aZrjJ5~)<^z_s0eFyl{>G0$e6On5FdJJss9bX#2Ma&KGhWH*QU4iI}4KLU+D6dMQ zgeSyc?}*U_@`c2c5*kf3n@s7zbUtlfS?C|8R;E9!h9kUGh(PYxGbSU>hXav6{MwL#WN>N++@Z>*Dkmi>S z)mu93V`A#}qaP~hhqCb14AL2X5-h$Z((s;y_vTXRCFcS3OYCE){cwT%;OS(@`%OO& zomC?heVFpSj*hE;q{{Yz_R|&j4*4Uxuj1Z! zy?(vAs{IG;L%2=ss5C9j>d)O%aqm0Jb$@h6dco4Hm%%5!{SQ#Q1u+EK(L3#JIk~q% zUxhCG^SNYRqIj5HCMxg zXD-b;fwC8Ln(!MT263<2k)tD-`_!-Va@)SBG91YGEZc$HO~yW6b8^wi=pF6mt3ch>XL;V7i3IFg>@Zu9sgNE|$wjm-R#Bx^=Slf!01mLU zccJ-dP+$s)-yjYbrddhb>GJ~|E==>_40q7{67W%UwWzAbP~K_ zkMQ@a+ADBR`y#%O5zB$xiKi0M|1NEZ*gvjl`^81v99CQq=_}h`j!dd(FSo36JJRuG zr7WQ z5rwxxbkw0fiTk(VK_f32ldczj7Cy&_x_V>hs&evicbz?tkllDN7P7V2|3S_!dGTc8 zf+DW`gF%cHEVgY-?HC&HDb(S(nH@kV34Vk+*uD;Hu~l`kZ5I_M8^$ndmL*DGxVH=) z-HT@=?I(~A-TnRZKR^SE>BpDwaWH<8@gBynG4?RN#5lYtv&3J(EDaIEWUuS%mQOyoQOp~*O z5XFpk#`TPijGtnBkZ})VH{*+pgN#Fr85DYekH%Qc_%X(hGrs;uNhZF)=MORVF!nLN z$@nM6Y0yHzQp)C;}D}IUDY$4v6yigL2u5fg_vU`DJK)hTZC?_7 zvgR(4BS8mEL{%jaIZ2_cYVN*pv_9+(d4mzvhojP^cC&DWHb$Ft|7CmZa)$9|UPTRRc0VY)@!JrFJIb^BkKQ4=|@Tu z2?Vqzm%l~Z?D9qRu+|WW`aPN_O5Q_rg+i_t%@q!NH~Oj5=4g7{ytf)7TS-hKTK5N{ z8yo)uPCQJArCZin+z<*hp~Y}74s?&obaJLM>h(v;oEXgbI4^4jlz+M2zD&PzhmUG! zs(B*eDpf{()a!#QmZO?H50k~A8qXayLLO0@t3?`N>rDYq)Te81 zmtPC`eJxtOuK5Bk4+zk{0P=K7#JL2)30~E$hr)uG+{<&6du&DT;oC|4x`~NZX{hYzc%s zuzz+|#7N1fd^yT84Z1NJj!@t6jH}2*oIiRu>W>ebQ90|)E_cM&B6yk*@p>YzO&FGi z#bx-n)uROiVW=_SclqMdVWtUdMRakaceC!tWQWfxFGekfu$&c@=Vr+iR3w@vXD8Kr zUcWmK3ekir%N51tR^lX2XQ~>ZZE1v^!H9i6=`l2ioV_%o%GK*uuGB($7+Ti0crj&0 z;U_|&KnS)C+BI_ec2=Tzig@q?ieX`m7DgeT2iCP&2d79hgl^QJR*b%g%kS1{^1sCu z)|WQB^&shHj$B_9))2>#%Ny1g!Ef<3&FzCfi+7q!gQ2UekzABKs#j5$M{m|hc#wr? zIJ3B`0~$IGdQYkXgExe=4JyX`sWdl>6+&ho^@n|4x31<*jDEQok|RZvR$I6hyfKzs zkw~a$_9BhYTOW<+>T;^Ru^6IK@o@+IoAr>fjEo6s#ToHGp#xp}wAt?&ohv0T4;UN40Kc<|B zRwO-{GS}I~npWGWYc6Gk)Emm7jC3b(3-W#;#XAr;3}26qX`M_PmhQq@iL4!c9MYQt zo8{cCxwM8T+*G1rXq9q{#hA|ui|2<&3QBpYl2W5kp^$zqqHlz8q3)1=chrj;5L)l6 zyRDJyNMrqa1IkB(9*7BFqBo;nTIZ@2=nXe7xrzASjS-)GpH_&xkyEJ~Ze53Noe`M0 zUXQf{mBa(}SJi!TKjR?d<>6;A^Rw!rd6c^-J&1JXrojP?=ZO2fxJkwT2&8Yk%S4E4LedOkA@4TyA-p3~2*RP?KBPmJ?w=~rUd|^4uYHMn$w8h%Ik}|9kVDCO(V2fJ& z%q*ByH2eBFtjENC&xq#ob$6rg-V}~DVK2y7FYs)v7Ca z2d$ccF=lHnY}Pi*guZZIy{Gh}b2MA?0$Y7a{YMv2@DPzfsC!O?_@Ln5 zn|I&Bfk?_z$ZJFP=S~Rm0C0I_?Mk9?Jc@mRh~6A2Xp(n@4qRV=>qcII%@!}C^6*Tt zUxjvmY)R{ybpg0r`xeILbkQ8;u+nvahGEx4OKBj4zVp=3iO!EM1 z$BAipq4~6$DTAim43X;@Cvs~oBDW+fIMbRgtY^jw^ZX1kI6;UHPZXjVdYXv}lFv;D zq0IzMW=TeH>I{@Uoi#LWaBQzRm|i!=k!E|T7qX&^>DEkf>-lkF8S2Za%@jE$pbBQx znesD5z9(1Y*XD@)`Pm{rls7bS&>A%1ODmSs>A^8|X||a;=_2P$max=9h^vGs!D0Cg zWFeW)XNt^MvW59nj?5pxwLH3pI*BfEj(`l%3ebwn&?fUWilYR@@Vi_YWpH1&iv!R|j*<#N5bWwQ5EM}a_5R)TgMUKZJ%(df%4GXz4 z9Q#$r?0W zlPRvj&(}>E${U>6Z|xl)%&E(^WjUn0^^i9Rd2h=Wx1rvZsB`)JOfd!Z=XjvM+AQH% z0e_047IDg?zvT0&AGQeV{E5Q)SoTonpwb8R?Rx6l>DgjBcuWV6>EJoNB!6hiU|#>k zUTbjbOpBOUn=2;HA1@|`pv_=b9hHOXglQsO%fvNciMnQ*kR~QPkkdQ!zVYI|S2D!4 zkaZeloCN*kqPvcjw^4|fafr8-tf}YY6S@VDgl@+PF`}#qaYGp;X*Om5r~|qr z`X?DCI|F){ zfqG_?OdHA{oYHHWVi8l&wkb6ehpdCL=N-y+Fvg*`X|R)NXxp?B*p3`?6EWr{V9Z%X z{$o7mGSg@r+h*pai@XSI*pngp9~9ynU&0U543VCxjcDtLeA6V0m{glBCe6<-w$8hu#4{9TL(D|Qmykz7>(&hQseUOLc zqU*`$DPGE<>A= zk9MS-Ps|V#!7~RoN&Qpajrtzt`ULq9)Q4+SXX-VqN0Mk&KUhR&O-2w^2osJ8lP#DR z#)-)&KN;mGmrNLJ?N#5Mu|E!5$MLxnM2&cnZwpa~!#JNH=U2s#%0LR?#Pxe{e4WeG zBK>6?Pw@FSkv^Psoz9OWT^~0MeqcW4ln}gha8e!dB{}~CG|xXF#EalVD91ugn!_~C zB*xX_ob(=2bFzu%(Y$yK#t8En{w@L(p9aHc6`Bz1L4MSPDC)`cpAwTz zJtBk7<(dQjcD`BUPRxqy3nisIvYd_1Qa`jIPdPQeV>+Zseml-r;&9;D1#Uqgv_26eFHvs9&w^jJ!P*6NS9S zB)WHz*NVJdNqL76>(qt3%YFVH=cTmr_c$-IT!hF=3&z-E9Ax}^oR`wi-{ZX0c=_)h z=QR-?mo^D`!;u~rik)f3-jZ{z9;v`Kn-D)5<7nY$xXxY`TVkROD;wo^cZKPfBwKAM%xgHV!j&gW9N!2I;#9HEnN5sz6 z`j(|l!AOhv4$~~BP2Bii<|^9s(8JTt@CM2W(Zgj_Eym&ul{u?;R|D-LR+zW2-bu&)%}O2nt~I(lianamy44DhuzZqz1sIeNiSwpw-q z?e@`5)uu$h;rkmp0=uAN*?kqHd)lbD1sfGkyB@-}K-7#FlH?9q=a-MQbr1E-EN*{| z3OL|L$nA+5*fI^CxShju;>D>D%xE^ zgQ_EXeAFC@=c5C~U64lEpBg{!lNMls)PZuFoBLpZ^3|AO<^Kog+(Ds=>-@CCE;rF5 zDY`(-7{|HDTODnx*F(+dN2gex8tktGVDSGt$qH5kDDFq&Hl^;VT##~B>9jfI z7WadO$29Co$wh6(_i#ue8SO*%r}7@>Ixlfk;!#81immI5c_X+wf9MqPDy8Cg2a%OkcXS^_n*YZ4zfG*?66 zNkTlBRA=0P#ob9vdExf-v#v64a^Li#O%fDd;> zNj0f$L29)e+znA&|H>uTafgB0jM+hmO4JtDGz~84;?!Pj6;Zl^h_6w4Sw_9elU%BE zyaUlUs`n0}jI?JSr+89mbN~aCBkrDroYmplP*fMwxy))z&Z^7`a8_Q| zA$Q(q(=4zCK~7DOWSP&kQM^k1Z7f*Wwtvnwtx^? zT}g4BoDE1sV>Z~G)M91R6>BT2tHoR{y|yI`&8X7vQO}Wo4~G$%g!P5W7XLtPBVPvP zdmFqMxo;4q;g97MK`WZ)lj=&IqdKYOlEKPAU{f@RwNEl7`}mH8mSykZv4Sx?hi7gIlU55z*KqZ zrQEAM;>M(0wXi~_$Q%PnRL9pMs;2@j*{4%V%r{l3nOYIA^wx(E5=QC|i=AU=#8F)6 zR(&lwdprEI=!CRYdVO>w;$93XZ(Ulmdg;p25~XtsA`dE^VFcU5cHB;-JZP&B`QeC1 zMjjw+r6J(-U|tLbTAYYH#mN4cXwsX)Son$CD{I!RuB~3Rbl!ZM;<-S`FFl~)P2$Ex z+Nq111;St(DWnz`oz&_1!s!n98@wB#M<*iOLV+z#1Z-`_m$Ow#)gi{}Z|=Og zsv(Q1jqV_9?Sp@Qc$ap%;g#WG48$y0TAY-AxG=*Oa!^0Y#Q z>4{4+)z8<1*Q@JsZX@VMJ2m*1ulV!)`&a*E{EhZte?_)KwOP=Y z(vIdxC^i+EsA=VX7%l}Gy?3|P;XKz#L`4t8D3kh~}e?IvO zRo}?|GR}wCZna-2I=rGNKJ1LAewg2YQ7_XWIwAGLbiGD9&M5gVnLn7$!gRgN zFEf=J+N&ho#dLOV$D~o{in!mbOg9=o!Thu<=&zaSI+<={I}H74Oc%QX-7c2zO|~mU zb)CG}0$mr=`B=Wu_~kQQKg(y#tA>z=@6BX`eA;}O!qp|VTU*M!*myUeDpBg zX!aLmenIZHoK$XcyYFOvuXBHl*52*R?+n*FvOkP*RK|L#yMo=X^2lpIyufYIX;%+WWO*SG4H=on0qziX^3 zRY=cAn(+HLs*uKgj+o$AGT`1%{4X37_lEm%foBs^HbOifkokmHQ;I`<3&(@VCtMMRoJc3fj#5nN{hx=Pkx$r(BYQ|;T8FY9D=_>8ydw$ut-#J* z*l8yk;F2#Y{g(qPA5ii*fI0sp#7fXuf!98W5ILl6z~^v~%*pYi6i50Bj%}bJ9RFps zm&yVUeFZWj-3`2Iw~|K#F8``ZJAkj^p!mN*U>f2>WxYTnu9T2sNBhAiIfj&CN#Dcq zKJp2#`a0@-N8wDQHKc98^lvDct-#k26KX?#a%||;op|bpV*~ODzlI}-bSE(TVb&qg zh!-WS!m%ARgh3n+Ax(G>j&7t|foXfV-+}+e=|LdHcha~{j`K{80Zon%r8v=BahwNF zLMIN(y9zx>=OaycH;y8tTY(pGlp!tlqc6H3BhtxnoXK&X6z}>VGPG&EyUfisj6F4E>9I!ihK@M4Iqw9NkFQ0YCRG$cuC* z@OwD=k*4qQSRdy;0$%rR*e~*nfJbpy&nx`t6VNfzgTSkwRJNo6YjMz+CA_W&^@64d z_*EPaA^i~0{vFsX(uA9Eke%-aev8wO1AoKmAz;B%iiU6@rxyb~oF;7H^rwKY;~@V@ zn2q>M;!mh?n$U}ba58#If7%{a(z3GqHB zneG94deQHoc?ozH2g&dzaQO+y@;m4sSdT*>jrTr@&*88jO-Qk>6gy5xajujmq*zr- z6H>hC=fR&aAIB?{{%`#;VSfW{fjUx~fp}VE*e;WgmyioPuF>~{zL;7_o-Mw)xyfgI zX?w}6dBwI_cwdYgTYvtIOJ-q5d~Vq+eCZl{U@jjvK9q<) zT*1H}UNQ^se^?Yo1W%JIJh#c~4h6!2hR9repL~%k+*G`I-Yj~bjkf{2J{&8fY9m%3 zF?iwRGBW;;&gz%_O}=@*WxsWQ{{Eu<&HG#Ti$RQeD8{z0Y+w04%l@)1N7wqU!LEy4 z;*jN#^-w;1FMY3Ug9(1G)81Lv+1T0H+11(G+1F`#IR9b$!*vgLKOB2_=;8c5>-RM7 z>EAQ3XK2sGJz}qQZ~k7}-m<;rdz<&}+Pi!2z}}&~<@>t!#r9p?SGT`$|Mva6_IK~^ z**~y}bRFnB(0^d)z{Lak2epIc2ki$N4+anJI=K5_ z&%xe=EnU_wTUQyRt?O#-+TPXG)!o(KH6SI|4iz1;A95TD9%??c`%veh-a~zd zVuywfSr6wQE<0R)xbAS{;q8Za9qvBdb9mtJ;9+sZa-`^p?TF*Z`XkLpT90%d={nMP zr2oj!k&8$2k7`HDkJ^tm9t|Gdb#(X9o};}-2am>%T8>$d*^ZSRTYs$XSnILv$GVPn zAL~ChaO~nSaa=oIbliU2aXfgu`S|YRoyU8R_Z^QNA39$Ch~ts{|TrXpn?DZ literal 0 HcmV?d00001 diff --git a/lib/yarl/_quoting_c.pyi b/lib/yarl/_quoting_c.pyi new file mode 100644 index 0000000..1c8fc24 --- /dev/null +++ b/lib/yarl/_quoting_c.pyi @@ -0,0 +1,16 @@ +from typing import Optional + +class _Quoter: + def __init__( + self, + *, + safe: str = ..., + protected: str = ..., + qs: bool = ..., + requote: bool = ... + ) -> None: ... + def __call__(self, val: Optional[str] = ...) -> Optional[str]: ... + +class _Unquoter: + def __init__(self, *, unsafe: str = ..., qs: bool = ...) -> None: ... + def __call__(self, val: Optional[str] = ...) -> Optional[str]: ... diff --git a/lib/yarl/_quoting_c.pyx b/lib/yarl/_quoting_c.pyx new file mode 100644 index 0000000..5335d17 --- /dev/null +++ b/lib/yarl/_quoting_c.pyx @@ -0,0 +1,371 @@ +# cython: language_level=3 + +from cpython.exc cimport PyErr_NoMemory +from cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc +from cpython.unicode cimport PyUnicode_DecodeASCII, PyUnicode_DecodeUTF8Stateful +from libc.stdint cimport uint8_t, uint64_t +from libc.string cimport memcpy, memset + +from string import ascii_letters, digits + + +cdef str GEN_DELIMS = ":/?#[]@" +cdef str SUB_DELIMS_WITHOUT_QS = "!$'()*," +cdef str SUB_DELIMS = SUB_DELIMS_WITHOUT_QS + '+?=;' +cdef str RESERVED = GEN_DELIMS + SUB_DELIMS +cdef str UNRESERVED = ascii_letters + digits + '-._~' +cdef str ALLOWED = UNRESERVED + SUB_DELIMS_WITHOUT_QS +cdef str QS = '+&=;' + +DEF BUF_SIZE = 8 * 1024 # 8KiB +cdef char BUFFER[BUF_SIZE] + +cdef inline Py_UCS4 _to_hex(uint8_t v): + if v < 10: + return (v+0x30) # ord('0') == 0x30 + else: + return (v+0x41-10) # ord('A') == 0x41 + + +cdef inline int _from_hex(Py_UCS4 v): + if '0' <= v <= '9': + return (v) - 0x30 # ord('0') == 0x30 + elif 'A' <= v <= 'F': + return (v) - 0x41 + 10 # ord('A') == 0x41 + elif 'a' <= v <= 'f': + return (v) - 0x61 + 10 # ord('a') == 0x61 + else: + return -1 + + +cdef inline int _is_lower_hex(Py_UCS4 v): + return 'a' <= v <= 'f' + + +cdef inline Py_UCS4 _restore_ch(Py_UCS4 d1, Py_UCS4 d2): + cdef int digit1 = _from_hex(d1) + if digit1 < 0: + return -1 + cdef int digit2 = _from_hex(d2) + if digit2 < 0: + return -1 + return (digit1 << 4 | digit2) + + +cdef uint8_t ALLOWED_TABLE[16] +cdef uint8_t ALLOWED_NOTQS_TABLE[16] + + +cdef inline bint bit_at(uint8_t array[], uint64_t ch): + return array[ch >> 3] & (1 << (ch & 7)) + + +cdef inline void set_bit(uint8_t array[], uint64_t ch): + array[ch >> 3] |= (1 << (ch & 7)) + + +memset(ALLOWED_TABLE, 0, sizeof(ALLOWED_TABLE)) +memset(ALLOWED_NOTQS_TABLE, 0, sizeof(ALLOWED_NOTQS_TABLE)) + +for i in range(128): + if chr(i) in ALLOWED: + set_bit(ALLOWED_TABLE, i) + set_bit(ALLOWED_NOTQS_TABLE, i) + if chr(i) in QS: + set_bit(ALLOWED_NOTQS_TABLE, i) + +# ----------------- writer --------------------------- + +cdef struct Writer: + char *buf + Py_ssize_t size + Py_ssize_t pos + bint changed + + +cdef inline void _init_writer(Writer* writer): + writer.buf = &BUFFER[0] + writer.size = BUF_SIZE + writer.pos = 0 + writer.changed = 0 + + +cdef inline void _release_writer(Writer* writer): + if writer.buf != BUFFER: + PyMem_Free(writer.buf) + + +cdef inline int _write_char(Writer* writer, Py_UCS4 ch, bint changed): + cdef char * buf + cdef Py_ssize_t size + + if writer.pos == writer.size: + # reallocate + size = writer.size + BUF_SIZE + if writer.buf == BUFFER: + buf = PyMem_Malloc(size) + if buf == NULL: + PyErr_NoMemory() + return -1 + memcpy(buf, writer.buf, writer.size) + else: + buf = PyMem_Realloc(writer.buf, size) + if buf == NULL: + PyErr_NoMemory() + return -1 + writer.buf = buf + writer.size = size + writer.buf[writer.pos] = ch + writer.pos += 1 + writer.changed |= changed + return 0 + + +cdef inline int _write_pct(Writer* writer, uint8_t ch, bint changed): + if _write_char(writer, '%', changed) < 0: + return -1 + if _write_char(writer, _to_hex(ch >> 4), changed) < 0: + return -1 + return _write_char(writer, _to_hex(ch & 0x0f), changed) + + +cdef inline int _write_utf8(Writer* writer, Py_UCS4 symbol): + cdef uint64_t utf = symbol + + if utf < 0x80: + return _write_pct(writer, utf, True) + elif utf < 0x800: + if _write_pct(writer, (0xc0 | (utf >> 6)), True) < 0: + return -1 + return _write_pct(writer, (0x80 | (utf & 0x3f)), True) + elif 0xD800 <= utf <= 0xDFFF: + # surogate pair, ignored + return 0 + elif utf < 0x10000: + if _write_pct(writer, (0xe0 | (utf >> 12)), True) < 0: + return -1 + if _write_pct(writer, (0x80 | ((utf >> 6) & 0x3f)), + True) < 0: + return -1 + return _write_pct(writer, (0x80 | (utf & 0x3f)), True) + elif utf > 0x10FFFF: + # symbol is too large + return 0 + else: + if _write_pct(writer, (0xf0 | (utf >> 18)), True) < 0: + return -1 + if _write_pct(writer, (0x80 | ((utf >> 12) & 0x3f)), + True) < 0: + return -1 + if _write_pct(writer, (0x80 | ((utf >> 6) & 0x3f)), + True) < 0: + return -1 + return _write_pct(writer, (0x80 | (utf & 0x3f)), True) + + +# --------------------- end writer -------------------------- + + +cdef class _Quoter: + cdef bint _qs + cdef bint _requote + + cdef uint8_t _safe_table[16] + cdef uint8_t _protected_table[16] + + def __init__( + self, *, str safe='', str protected='', bint qs=False, bint requote=True, + ): + cdef Py_UCS4 ch + + self._qs = qs + self._requote = requote + + if not self._qs: + memcpy(self._safe_table, + ALLOWED_NOTQS_TABLE, + sizeof(self._safe_table)) + else: + memcpy(self._safe_table, + ALLOWED_TABLE, + sizeof(self._safe_table)) + for ch in safe: + if ord(ch) > 127: + raise ValueError("Only safe symbols with ORD < 128 are allowed") + set_bit(self._safe_table, ch) + + memset(self._protected_table, 0, sizeof(self._protected_table)) + for ch in protected: + if ord(ch) > 127: + raise ValueError("Only safe symbols with ORD < 128 are allowed") + set_bit(self._safe_table, ch) + set_bit(self._protected_table, ch) + + def __call__(self, val): + cdef Writer writer + if val is None: + return None + if type(val) is not str: + if isinstance(val, str): + # derived from str + val = str(val) + else: + raise TypeError("Argument should be str") + _init_writer(&writer) + try: + return self._do_quote(val, &writer) + finally: + _release_writer(&writer) + + cdef str _do_quote(self, str val, Writer *writer): + cdef Py_UCS4 ch + cdef int changed + cdef int idx = 0 + cdef int length = len(val) + + while idx < length: + ch = val[idx] + idx += 1 + if ch == '%' and self._requote and idx <= length - 2: + ch = _restore_ch(val[idx], val[idx + 1]) + if ch != -1: + idx += 2 + if ch < 128: + if bit_at(self._protected_table, ch): + if _write_pct(writer, ch, True) < 0: + raise + continue + + if bit_at(self._safe_table, ch): + if _write_char(writer, ch, True) < 0: + raise + continue + + changed = (_is_lower_hex(val[idx - 2]) or + _is_lower_hex(val[idx - 1])) + if _write_pct(writer, ch, changed) < 0: + raise + continue + else: + ch = '%' + + if self._write(writer, ch) < 0: + raise + + if not writer.changed: + return val + else: + return PyUnicode_DecodeASCII(writer.buf, writer.pos, "strict") + + cdef inline int _write(self, Writer *writer, Py_UCS4 ch): + if self._qs: + if ch == ' ': + return _write_char(writer, '+', True) + + if ch < 128 and bit_at(self._safe_table, ch): + return _write_char(writer, ch, False) + + return _write_utf8(writer, ch) + + +cdef class _Unquoter: + cdef str _unsafe + cdef bint _qs + cdef _Quoter _quoter + cdef _Quoter _qs_quoter + + def __init__(self, *, unsafe='', qs=False): + self._unsafe = unsafe + self._qs = qs + self._quoter = _Quoter() + self._qs_quoter = _Quoter(qs=True) + + def __call__(self, val): + if val is None: + return None + if type(val) is not str: + if isinstance(val, str): + # derived from str + val = str(val) + else: + raise TypeError("Argument should be str") + return self._do_unquote(val) + + cdef str _do_unquote(self, str val): + if len(val) == 0: + return val + cdef list ret = [] + cdef char buffer[4] + cdef Py_ssize_t buflen = 0 + cdef Py_ssize_t consumed + cdef str unquoted + cdef Py_UCS4 ch = 0 + cdef Py_ssize_t idx = 0 + cdef Py_ssize_t length = len(val) + cdef Py_ssize_t start_pct + + while idx < length: + ch = val[idx] + idx += 1 + if ch == '%' and idx <= length - 2: + ch = _restore_ch(val[idx], val[idx + 1]) + if ch != -1: + idx += 2 + assert buflen < 4 + buffer[buflen] = ch + buflen += 1 + try: + unquoted = PyUnicode_DecodeUTF8Stateful(buffer, buflen, + NULL, &consumed) + except UnicodeDecodeError: + start_pct = idx - buflen * 3 + buffer[0] = ch + buflen = 1 + ret.append(val[start_pct : idx - 3]) + try: + unquoted = PyUnicode_DecodeUTF8Stateful(buffer, buflen, + NULL, &consumed) + except UnicodeDecodeError: + buflen = 0 + ret.append(val[idx - 3 : idx]) + continue + if not unquoted: + assert consumed == 0 + continue + assert consumed == buflen + buflen = 0 + if self._qs and unquoted in '+=&;': + ret.append(self._qs_quoter(unquoted)) + elif unquoted in self._unsafe: + ret.append(self._quoter(unquoted)) + else: + ret.append(unquoted) + continue + else: + ch = '%' + + if buflen: + start_pct = idx - 1 - buflen * 3 + ret.append(val[start_pct : idx - 1]) + buflen = 0 + + if ch == '+': + if not self._qs or ch in self._unsafe: + ret.append('+') + else: + ret.append(' ') + continue + + if ch in self._unsafe: + ret.append('%') + h = hex(ord(ch)).upper()[2:] + for ch in h: + ret.append(ch) + continue + + ret.append(ch) + + if buflen: + ret.append(val[length - buflen * 3 : length]) + + return ''.join(ret) diff --git a/lib/yarl/_quoting_py.py b/lib/yarl/_quoting_py.py new file mode 100644 index 0000000..585a1da --- /dev/null +++ b/lib/yarl/_quoting_py.py @@ -0,0 +1,197 @@ +import codecs +import re +from string import ascii_letters, ascii_lowercase, digits +from typing import Optional, cast + +BASCII_LOWERCASE = ascii_lowercase.encode("ascii") +BPCT_ALLOWED = {f"%{i:02X}".encode("ascii") for i in range(256)} +GEN_DELIMS = ":/?#[]@" +SUB_DELIMS_WITHOUT_QS = "!$'()*," +SUB_DELIMS = SUB_DELIMS_WITHOUT_QS + "+&=;" +RESERVED = GEN_DELIMS + SUB_DELIMS +UNRESERVED = ascii_letters + digits + "-._~" +ALLOWED = UNRESERVED + SUB_DELIMS_WITHOUT_QS + + +_IS_HEX = re.compile(b"[A-Z0-9][A-Z0-9]") +_IS_HEX_STR = re.compile("[A-Fa-f0-9][A-Fa-f0-9]") + +utf8_decoder = codecs.getincrementaldecoder("utf-8") + + +class _Quoter: + def __init__( + self, + *, + safe: str = "", + protected: str = "", + qs: bool = False, + requote: bool = True, + ) -> None: + self._safe = safe + self._protected = protected + self._qs = qs + self._requote = requote + + def __call__(self, val: Optional[str]) -> Optional[str]: + if val is None: + return None + if not isinstance(val, str): + raise TypeError("Argument should be str") + if not val: + return "" + bval = cast(str, val).encode("utf8", errors="ignore") + ret = bytearray() + pct = bytearray() + safe = self._safe + safe += ALLOWED + if not self._qs: + safe += "+&=;" + safe += self._protected + bsafe = safe.encode("ascii") + idx = 0 + while idx < len(bval): + ch = bval[idx] + idx += 1 + + if pct: + if ch in BASCII_LOWERCASE: + ch = ch - 32 # convert to uppercase + pct.append(ch) + if len(pct) == 3: # pragma: no branch # peephole optimizer + buf = pct[1:] + if not _IS_HEX.match(buf): + ret.extend(b"%25") + pct.clear() + idx -= 2 + continue + try: + unquoted = chr(int(pct[1:].decode("ascii"), base=16)) + except ValueError: + ret.extend(b"%25") + pct.clear() + idx -= 2 + continue + + if unquoted in self._protected: + ret.extend(pct) + elif unquoted in safe: + ret.append(ord(unquoted)) + else: + ret.extend(pct) + pct.clear() + + # special case, if we have only one char after "%" + elif len(pct) == 2 and idx == len(bval): + ret.extend(b"%25") + pct.clear() + idx -= 1 + + continue + + elif ch == ord("%") and self._requote: + pct.clear() + pct.append(ch) + + # special case if "%" is last char + if idx == len(bval): + ret.extend(b"%25") + + continue + + if self._qs: + if ch == ord(" "): + ret.append(ord("+")) + continue + if ch in bsafe: + ret.append(ch) + continue + + ret.extend((f"%{ch:02X}").encode("ascii")) + + ret2 = ret.decode("ascii") + if ret2 == val: + return val + return ret2 + + +class _Unquoter: + def __init__(self, *, unsafe: str = "", qs: bool = False) -> None: + self._unsafe = unsafe + self._qs = qs + self._quoter = _Quoter() + self._qs_quoter = _Quoter(qs=True) + + def __call__(self, val: Optional[str]) -> Optional[str]: + if val is None: + return None + if not isinstance(val, str): + raise TypeError("Argument should be str") + if not val: + return "" + decoder = cast(codecs.BufferedIncrementalDecoder, utf8_decoder()) + ret = [] + idx = 0 + while idx < len(val): + ch = val[idx] + idx += 1 + if ch == "%" and idx <= len(val) - 2: + pct = val[idx : idx + 2] + if _IS_HEX_STR.fullmatch(pct): + b = bytes([int(pct, base=16)]) + idx += 2 + try: + unquoted = decoder.decode(b) + except UnicodeDecodeError: + start_pct = idx - 3 - len(decoder.buffer) * 3 + ret.append(val[start_pct : idx - 3]) + decoder.reset() + try: + unquoted = decoder.decode(b) + except UnicodeDecodeError: + ret.append(val[idx - 3 : idx]) + continue + if not unquoted: + continue + if self._qs and unquoted in "+=&;": + to_add = self._qs_quoter(unquoted) + if to_add is None: # pragma: no cover + raise RuntimeError("Cannot quote None") + ret.append(to_add) + elif unquoted in self._unsafe: + to_add = self._quoter(unquoted) + if to_add is None: # pragma: no cover + raise RuntimeError("Cannot quote None") + ret.append(to_add) + else: + ret.append(unquoted) + continue + + if decoder.buffer: + start_pct = idx - 1 - len(decoder.buffer) * 3 + ret.append(val[start_pct : idx - 1]) + decoder.reset() + + if ch == "+": + if not self._qs or ch in self._unsafe: + ret.append("+") + else: + ret.append(" ") + continue + + if ch in self._unsafe: + ret.append("%") + h = hex(ord(ch)).upper()[2:] + for ch in h: + ret.append(ch) + continue + + ret.append(ch) + + if decoder.buffer: + ret.append(val[-len(decoder.buffer) * 3 :]) + + ret2 = "".join(ret) + if ret2 == val: + return val + return ret2 diff --git a/lib/yarl/_url.py b/lib/yarl/_url.py new file mode 100644 index 0000000..dfcff2e --- /dev/null +++ b/lib/yarl/_url.py @@ -0,0 +1,1159 @@ +import functools +import math +import warnings +from collections.abc import Mapping, Sequence +from ipaddress import ip_address +from urllib.parse import SplitResult, parse_qsl, quote, urljoin, urlsplit, urlunsplit + +import idna +from multidict import MultiDict, MultiDictProxy + +from ._quoting import _Quoter, _Unquoter + +DEFAULT_PORTS = {"http": 80, "https": 443, "ws": 80, "wss": 443} + +sentinel = object() + + +def rewrite_module(obj: object) -> object: + obj.__module__ = "yarl" + return obj + + +class cached_property: + """Use as a class method decorator. It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + + """ + + def __init__(self, wrapped): + self.wrapped = wrapped + try: + self.__doc__ = wrapped.__doc__ + except AttributeError: # pragma: no cover + self.__doc__ = "" + self.name = wrapped.__name__ + + def __get__(self, inst, owner, _sentinel=sentinel): + if inst is None: + return self + val = inst._cache.get(self.name, _sentinel) + if val is not _sentinel: + return val + val = self.wrapped(inst) + inst._cache[self.name] = val + return val + + def __set__(self, inst, value): + raise AttributeError("cached property is read-only") + + +@rewrite_module +class URL: + # Don't derive from str + # follow pathlib.Path design + # probably URL will not suffer from pathlib problems: + # it's intended for libraries like aiohttp, + # not to be passed into standard library functions like os.open etc. + + # URL grammar (RFC 3986) + # pct-encoded = "%" HEXDIG HEXDIG + # reserved = gen-delims / sub-delims + # gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + # / "*" / "+" / "," / ";" / "=" + # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + # URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + # hier-part = "//" authority path-abempty + # / path-absolute + # / path-rootless + # / path-empty + # scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + # authority = [ userinfo "@" ] host [ ":" port ] + # userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + # host = IP-literal / IPv4address / reg-name + # IP-literal = "[" ( IPv6address / IPvFuture ) "]" + # IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + # IPv6address = 6( h16 ":" ) ls32 + # / "::" 5( h16 ":" ) ls32 + # / [ h16 ] "::" 4( h16 ":" ) ls32 + # / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + # / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + # / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + # / [ *4( h16 ":" ) h16 ] "::" ls32 + # / [ *5( h16 ":" ) h16 ] "::" h16 + # / [ *6( h16 ":" ) h16 ] "::" + # ls32 = ( h16 ":" h16 ) / IPv4address + # ; least-significant 32 bits of address + # h16 = 1*4HEXDIG + # ; 16 bits of address represented in hexadecimal + # IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + # dec-octet = DIGIT ; 0-9 + # / %x31-39 DIGIT ; 10-99 + # / "1" 2DIGIT ; 100-199 + # / "2" %x30-34 DIGIT ; 200-249 + # / "25" %x30-35 ; 250-255 + # reg-name = *( unreserved / pct-encoded / sub-delims ) + # port = *DIGIT + # path = path-abempty ; begins with "/" or is empty + # / path-absolute ; begins with "/" but not "//" + # / path-noscheme ; begins with a non-colon segment + # / path-rootless ; begins with a segment + # / path-empty ; zero characters + # path-abempty = *( "/" segment ) + # path-absolute = "/" [ segment-nz *( "/" segment ) ] + # path-noscheme = segment-nz-nc *( "/" segment ) + # path-rootless = segment-nz *( "/" segment ) + # path-empty = 0 + # segment = *pchar + # segment-nz = 1*pchar + # segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) + # ; non-zero-length segment without any colon ":" + # pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + # query = *( pchar / "/" / "?" ) + # fragment = *( pchar / "/" / "?" ) + # URI-reference = URI / relative-ref + # relative-ref = relative-part [ "?" query ] [ "#" fragment ] + # relative-part = "//" authority path-abempty + # / path-absolute + # / path-noscheme + # / path-empty + # absolute-URI = scheme ":" hier-part [ "?" query ] + __slots__ = ("_cache", "_val") + + _QUOTER = _Quoter(requote=False) + _REQUOTER = _Quoter() + _PATH_QUOTER = _Quoter(safe="@:", protected="/+", requote=False) + _PATH_REQUOTER = _Quoter(safe="@:", protected="/+") + _QUERY_QUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True, requote=False) + _QUERY_REQUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True) + _QUERY_PART_QUOTER = _Quoter(safe="?/:@", qs=True, requote=False) + _FRAGMENT_QUOTER = _Quoter(safe="?/:@", requote=False) + _FRAGMENT_REQUOTER = _Quoter(safe="?/:@") + + _UNQUOTER = _Unquoter() + _PATH_UNQUOTER = _Unquoter(unsafe="+") + _QS_UNQUOTER = _Unquoter(qs=True) + + def __new__(cls, val="", *, encoded=False, strict=None): + if strict is not None: # pragma: no cover + warnings.warn("strict parameter is ignored") + if type(val) is cls: + return val + if type(val) is str: + val = urlsplit(val) + elif type(val) is SplitResult: + if not encoded: + raise ValueError("Cannot apply decoding to SplitResult") + elif isinstance(val, str): + val = urlsplit(str(val)) + else: + raise TypeError("Constructor parameter should be str") + + if not encoded: + if not val[1]: # netloc + netloc = "" + host = "" + else: + host = val.hostname + if host is None: + raise ValueError("Invalid URL: host is required for absolute urls") + + try: + port = val.port + except ValueError as e: + raise ValueError( + "Invalid URL: port can't be converted to integer" + ) from e + + netloc = cls._make_netloc( + val.username, val.password, host, port, encode=True, requote=True + ) + path = cls._PATH_REQUOTER(val[2]) + if netloc: + path = cls._normalize_path(path) + + cls._validate_authority_uri_abs_path(host=host, path=path) + query = cls._QUERY_REQUOTER(val[3]) + fragment = cls._FRAGMENT_REQUOTER(val[4]) + val = SplitResult(val[0], netloc, path, query, fragment) + + self = object.__new__(cls) + self._val = val + self._cache = {} + return self + + @classmethod + def build( + cls, + *, + scheme="", + authority="", + user=None, + password=None, + host="", + port=None, + path="", + query=None, + query_string="", + fragment="", + encoded=False, + ): + """Creates and returns a new URL""" + + if authority and (user or password or host or port): + raise ValueError( + 'Can\'t mix "authority" with "user", "password", "host" or "port".' + ) + if port and not host: + raise ValueError('Can\'t build URL with "port" but without "host".') + if query and query_string: + raise ValueError('Only one of "query" or "query_string" should be passed') + if ( + scheme is None + or authority is None + or path is None + or query_string is None + or fragment is None + ): + raise TypeError( + 'NoneType is illegal for "scheme", "authority", "path", ' + '"query_string", and "fragment" args, use empty string instead.' + ) + + if authority: + if encoded: + netloc = authority + else: + tmp = SplitResult("", authority, "", "", "") + netloc = cls._make_netloc( + tmp.username, tmp.password, tmp.hostname, tmp.port, encode=True + ) + elif not user and not password and not host and not port: + netloc = "" + else: + netloc = cls._make_netloc( + user, password, host, port, encode=not encoded, encode_host=not encoded + ) + if not encoded: + path = cls._PATH_QUOTER(path) + if netloc: + path = cls._normalize_path(path) + + cls._validate_authority_uri_abs_path(host=host, path=path) + query_string = cls._QUERY_QUOTER(query_string) + fragment = cls._FRAGMENT_QUOTER(fragment) + + url = cls( + SplitResult(scheme, netloc, path, query_string, fragment), encoded=True + ) + + if query: + return url.with_query(query) + else: + return url + + def __init_subclass__(cls): + raise TypeError(f"Inheriting a class {cls!r} from URL is forbidden") + + def __str__(self): + val = self._val + if not val.path and self.is_absolute() and (val.query or val.fragment): + val = val._replace(path="/") + return urlunsplit(val) + + def __repr__(self): + return f"{self.__class__.__name__}('{str(self)}')" + + def __bytes__(self): + return str(self).encode("ascii") + + def __eq__(self, other): + if not type(other) is URL: + return NotImplemented + + val1 = self._val + if not val1.path and self.is_absolute(): + val1 = val1._replace(path="/") + + val2 = other._val + if not val2.path and other.is_absolute(): + val2 = val2._replace(path="/") + + return val1 == val2 + + def __hash__(self): + ret = self._cache.get("hash") + if ret is None: + val = self._val + if not val.path and self.is_absolute(): + val = val._replace(path="/") + ret = self._cache["hash"] = hash(val) + return ret + + def __le__(self, other): + if not type(other) is URL: + return NotImplemented + return self._val <= other._val + + def __lt__(self, other): + if not type(other) is URL: + return NotImplemented + return self._val < other._val + + def __ge__(self, other): + if not type(other) is URL: + return NotImplemented + return self._val >= other._val + + def __gt__(self, other): + if not type(other) is URL: + return NotImplemented + return self._val > other._val + + def __truediv__(self, name): + name = self._PATH_QUOTER(name) + if name.startswith("/"): + raise ValueError( + f"Appending path {name!r} starting from slash is forbidden" + ) + path = self._val.path + if path == "/": + new_path = "/" + name + elif not path and not self.is_absolute(): + new_path = name + else: + parts = path.rstrip("/").split("/") + parts.append(name) + new_path = "/".join(parts) + if self.is_absolute(): + new_path = self._normalize_path(new_path) + return URL( + self._val._replace(path=new_path, query="", fragment=""), encoded=True + ) + + def __mod__(self, query): + return self.update_query(query) + + def __bool__(self) -> bool: + return bool( + self._val.netloc or self._val.path or self._val.query or self._val.fragment + ) + + def __getstate__(self): + return (self._val,) + + def __setstate__(self, state): + if state[0] is None and isinstance(state[1], dict): + # default style pickle + self._val = state[1]["_val"] + else: + self._val, *unused = state + self._cache = {} + + def is_absolute(self): + """A check for absolute URLs. + + Return True for absolute ones (having scheme or starting + with //), False otherwise. + + """ + return self.raw_host is not None + + def is_default_port(self): + """A check for default port. + + Return True if port is default for specified scheme, + e.g. 'http://python.org' or 'http://python.org:80', False + otherwise. + + """ + if self.port is None: + return False + default = DEFAULT_PORTS.get(self.scheme) + if default is None: + return False + return self.port == default + + def origin(self): + """Return an URL with scheme, host and port parts only. + + user, password, path, query and fragment are removed. + + """ + # TODO: add a keyword-only option for keeping user/pass maybe? + if not self.is_absolute(): + raise ValueError("URL should be absolute") + if not self._val.scheme: + raise ValueError("URL should have scheme") + v = self._val + netloc = self._make_netloc(None, None, v.hostname, v.port) + val = v._replace(netloc=netloc, path="", query="", fragment="") + return URL(val, encoded=True) + + def relative(self): + """Return a relative part of the URL. + + scheme, user, password, host and port are removed. + + """ + if not self.is_absolute(): + raise ValueError("URL should be absolute") + val = self._val._replace(scheme="", netloc="") + return URL(val, encoded=True) + + @property + def scheme(self): + """Scheme for absolute URLs. + + Empty string for relative URLs or URLs starting with // + + """ + return self._val.scheme + + @property + def raw_authority(self): + """Encoded authority part of URL. + + Empty string for relative URLs. + + """ + return self._val.netloc + + @cached_property + def authority(self): + """Decoded authority part of URL. + + Empty string for relative URLs. + + """ + return self._make_netloc( + self.user, self.password, self.host, self.port, encode_host=False + ) + + @property + def raw_user(self): + """Encoded user part of URL. + + None if user is missing. + + """ + # not .username + ret = self._val.username + if not ret: + return None + return ret + + @cached_property + def user(self): + """Decoded user part of URL. + + None if user is missing. + + """ + return self._UNQUOTER(self.raw_user) + + @property + def raw_password(self): + """Encoded password part of URL. + + None if password is missing. + + """ + return self._val.password + + @cached_property + def password(self): + """Decoded password part of URL. + + None if password is missing. + + """ + return self._UNQUOTER(self.raw_password) + + @property + def raw_host(self): + """Encoded host part of URL. + + None for relative URLs. + + """ + # Use host instead of hostname for sake of shortness + # May add .hostname prop later + return self._val.hostname + + @cached_property + def host(self): + """Decoded host part of URL. + + None for relative URLs. + + """ + raw = self.raw_host + if raw is None: + return None + if "%" in raw: + # Hack for scoped IPv6 addresses like + # fe80::2%Проверка + # presence of '%' sign means only IPv6 address, so idna is useless. + return raw + return _idna_decode(raw) + + @property + def port(self): + """Port part of URL, with scheme-based fallback. + + None for relative URLs or URLs without explicit port and + scheme without default port substitution. + + """ + return self._val.port or DEFAULT_PORTS.get(self._val.scheme) + + @property + def explicit_port(self): + """Port part of URL, without scheme-based fallback. + + None for relative URLs or URLs without explicit port. + + """ + return self._val.port + + @property + def raw_path(self): + """Encoded path of URL. + + / for absolute URLs without path part. + + """ + ret = self._val.path + if not ret and self.is_absolute(): + ret = "/" + return ret + + @cached_property + def path(self): + """Decoded path of URL. + + / for absolute URLs without path part. + + """ + return self._PATH_UNQUOTER(self.raw_path) + + @cached_property + def query(self): + """A MultiDictProxy representing parsed query parameters in decoded + representation. + + Empty value if URL has no query part. + + """ + ret = MultiDict(parse_qsl(self.raw_query_string, keep_blank_values=True)) + return MultiDictProxy(ret) + + @property + def raw_query_string(self): + """Encoded query part of URL. + + Empty string if query is missing. + + """ + return self._val.query + + @cached_property + def query_string(self): + """Decoded query part of URL. + + Empty string if query is missing. + + """ + return self._QS_UNQUOTER(self.raw_query_string) + + @cached_property + def path_qs(self): + """Decoded path of URL with query.""" + if not self.query_string: + return self.path + return f"{self.path}?{self.query_string}" + + @cached_property + def raw_path_qs(self): + """Encoded path of URL with query.""" + if not self.raw_query_string: + return self.raw_path + return f"{self.raw_path}?{self.raw_query_string}" + + @property + def raw_fragment(self): + """Encoded fragment part of URL. + + Empty string if fragment is missing. + + """ + return self._val.fragment + + @cached_property + def fragment(self): + """Decoded fragment part of URL. + + Empty string if fragment is missing. + + """ + return self._UNQUOTER(self.raw_fragment) + + @cached_property + def raw_parts(self): + """A tuple containing encoded *path* parts. + + ('/',) for absolute URLs if *path* is missing. + + """ + path = self._val.path + if self.is_absolute(): + if not path: + parts = ["/"] + else: + parts = ["/"] + path[1:].split("/") + else: + if path.startswith("/"): + parts = ["/"] + path[1:].split("/") + else: + parts = path.split("/") + return tuple(parts) + + @cached_property + def parts(self): + """A tuple containing decoded *path* parts. + + ('/',) for absolute URLs if *path* is missing. + + """ + return tuple(self._UNQUOTER(part) for part in self.raw_parts) + + @cached_property + def parent(self): + """A new URL with last part of path removed and cleaned up query and + fragment. + + """ + path = self.raw_path + if not path or path == "/": + if self.raw_fragment or self.raw_query_string: + return URL(self._val._replace(query="", fragment=""), encoded=True) + return self + parts = path.split("/") + val = self._val._replace(path="/".join(parts[:-1]), query="", fragment="") + return URL(val, encoded=True) + + @cached_property + def raw_name(self): + """The last part of raw_parts.""" + parts = self.raw_parts + if self.is_absolute(): + parts = parts[1:] + if not parts: + return "" + else: + return parts[-1] + else: + return parts[-1] + + @cached_property + def name(self): + """The last part of parts.""" + return self._UNQUOTER(self.raw_name) + + @cached_property + def raw_suffix(self): + name = self.raw_name + i = name.rfind(".") + if 0 < i < len(name) - 1: + return name[i:] + else: + return "" + + @cached_property + def suffix(self): + return self._UNQUOTER(self.raw_suffix) + + @cached_property + def raw_suffixes(self): + name = self.raw_name + if name.endswith("."): + return () + name = name.lstrip(".") + return tuple("." + suffix for suffix in name.split(".")[1:]) + + @cached_property + def suffixes(self): + return tuple(self._UNQUOTER(suffix) for suffix in self.raw_suffixes) + + @staticmethod + def _validate_authority_uri_abs_path(host, path): + """Ensure that path in URL with authority starts with a leading slash. + + Raise ValueError if not. + """ + if len(host) > 0 and len(path) > 0 and not path.startswith("/"): + raise ValueError( + "Path in a URL with authority should start with a slash ('/') if set" + ) + + @classmethod + def _normalize_path(cls, path): + # Drop '.' and '..' from path + + segments = path.split("/") + resolved_path = [] + + for seg in segments: + if seg == "..": + try: + resolved_path.pop() + except IndexError: + # ignore any .. segments that would otherwise cause an + # IndexError when popped from resolved_path if + # resolving for rfc3986 + pass + elif seg == ".": + continue + else: + resolved_path.append(seg) + + if segments[-1] in (".", ".."): + # do some post-processing here. + # if the last segment was a relative dir, + # then we need to append the trailing '/' + resolved_path.append("") + + return "/".join(resolved_path) + + @classmethod + def _encode_host(cls, host, human=False): + try: + ip, sep, zone = host.partition("%") + ip = ip_address(ip) + except ValueError: + host = host.lower() + # IDNA encoding is slow, + # skip it for ASCII-only strings + # Don't move the check into _idna_encode() helper + # to reduce the cache size + if human or host.isascii(): + return host + host = _idna_encode(host) + else: + host = ip.compressed + if sep: + host += "%" + zone + if ip.version == 6: + host = "[" + host + "]" + return host + + @classmethod + def _make_netloc( + cls, user, password, host, port, encode=False, encode_host=True, requote=False + ): + quoter = cls._REQUOTER if requote else cls._QUOTER + if encode_host: + ret = cls._encode_host(host) + else: + ret = host + if port: + ret = ret + ":" + str(port) + if password is not None: + if not user: + user = "" + else: + if encode: + user = quoter(user) + if encode: + password = quoter(password) + user = user + ":" + password + elif user and encode: + user = quoter(user) + if user: + ret = user + "@" + ret + return ret + + def with_scheme(self, scheme): + """Return a new URL with scheme replaced.""" + # N.B. doesn't cleanup query/fragment + if not isinstance(scheme, str): + raise TypeError("Invalid scheme type") + if not self.is_absolute(): + raise ValueError("scheme replacement is not allowed for relative URLs") + return URL(self._val._replace(scheme=scheme.lower()), encoded=True) + + def with_user(self, user): + """Return a new URL with user replaced. + + Autoencode user if needed. + + Clear user/password if user is None. + + """ + # N.B. doesn't cleanup query/fragment + val = self._val + if user is None: + password = None + elif isinstance(user, str): + user = self._QUOTER(user) + password = val.password + else: + raise TypeError("Invalid user type") + if not self.is_absolute(): + raise ValueError("user replacement is not allowed for relative URLs") + return URL( + self._val._replace( + netloc=self._make_netloc(user, password, val.hostname, val.port) + ), + encoded=True, + ) + + def with_password(self, password): + """Return a new URL with password replaced. + + Autoencode password if needed. + + Clear password if argument is None. + + """ + # N.B. doesn't cleanup query/fragment + if password is None: + pass + elif isinstance(password, str): + password = self._QUOTER(password) + else: + raise TypeError("Invalid password type") + if not self.is_absolute(): + raise ValueError("password replacement is not allowed for relative URLs") + val = self._val + return URL( + self._val._replace( + netloc=self._make_netloc(val.username, password, val.hostname, val.port) + ), + encoded=True, + ) + + def with_host(self, host): + """Return a new URL with host replaced. + + Autoencode host if needed. + + Changing host for relative URLs is not allowed, use .join() + instead. + + """ + # N.B. doesn't cleanup query/fragment + if not isinstance(host, str): + raise TypeError("Invalid host type") + if not self.is_absolute(): + raise ValueError("host replacement is not allowed for relative URLs") + if not host: + raise ValueError("host removing is not allowed") + val = self._val + return URL( + self._val._replace( + netloc=self._make_netloc(val.username, val.password, host, val.port) + ), + encoded=True, + ) + + def with_port(self, port): + """Return a new URL with port replaced. + + Clear port to default if None is passed. + + """ + # N.B. doesn't cleanup query/fragment + if port is not None and not isinstance(port, int): + raise TypeError(f"port should be int or None, got {type(port)}") + if not self.is_absolute(): + raise ValueError("port replacement is not allowed for relative URLs") + val = self._val + return URL( + self._val._replace( + netloc=self._make_netloc(val.username, val.password, val.hostname, port) + ), + encoded=True, + ) + + def with_path(self, path, *, encoded=False): + """Return a new URL with path replaced.""" + if not encoded: + path = self._PATH_QUOTER(path) + if self.is_absolute(): + path = self._normalize_path(path) + if len(path) > 0 and path[0] != "/": + path = "/" + path + return URL(self._val._replace(path=path, query="", fragment=""), encoded=True) + + @classmethod + def _query_seq_pairs(cls, quoter, pairs): + for key, val in pairs: + if isinstance(val, (list, tuple)): + for v in val: + yield quoter(key) + "=" + quoter(cls._query_var(v)) + else: + yield quoter(key) + "=" + quoter(cls._query_var(val)) + + @staticmethod + def _query_var(v): + cls = type(v) + if issubclass(cls, str): + return v + if issubclass(cls, float): + if math.isinf(v): + raise ValueError("float('inf') is not supported") + if math.isnan(v): + raise ValueError("float('nan') is not supported") + return str(float(v)) + if issubclass(cls, int) and cls is not bool: + return str(int(v)) + raise TypeError( + "Invalid variable type: value " + "should be str, int or float, got {!r} " + "of type {}".format(v, cls) + ) + + def _get_str_query(self, *args, **kwargs): + if kwargs: + if len(args) > 0: + raise ValueError( + "Either kwargs or single query parameter must be present" + ) + query = kwargs + elif len(args) == 1: + query = args[0] + else: + raise ValueError("Either kwargs or single query parameter must be present") + + if query is None: + query = "" + elif isinstance(query, Mapping): + quoter = self._QUERY_PART_QUOTER + query = "&".join(self._query_seq_pairs(quoter, query.items())) + elif isinstance(query, str): + query = self._QUERY_QUOTER(query) + elif isinstance(query, (bytes, bytearray, memoryview)): + raise TypeError( + "Invalid query type: bytes, bytearray and memoryview are forbidden" + ) + elif isinstance(query, Sequence): + quoter = self._QUERY_PART_QUOTER + # We don't expect sequence values if we're given a list of pairs + # already; only mappings like builtin `dict` which can't have the + # same key pointing to multiple values are allowed to use + # `_query_seq_pairs`. + query = "&".join( + quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query + ) + else: + raise TypeError( + "Invalid query type: only str, mapping or " + "sequence of (key, value) pairs is allowed" + ) + + return query + + def with_query(self, *args, **kwargs): + """Return a new URL with query part replaced. + + Accepts any Mapping (e.g. dict, multidict.MultiDict instances) + or str, autoencode the argument if needed. + + A sequence of (key, value) pairs is supported as well. + + It also can take an arbitrary number of keyword arguments. + + Clear query if None is passed. + + """ + # N.B. doesn't cleanup query/fragment + + new_query = self._get_str_query(*args, **kwargs) + return URL( + self._val._replace(path=self._val.path, query=new_query), encoded=True + ) + + def update_query(self, *args, **kwargs): + """Return a new URL with query part updated.""" + s = self._get_str_query(*args, **kwargs) + new_query = MultiDict(parse_qsl(s, keep_blank_values=True)) + query = MultiDict(self.query) + query.update(new_query) + + return URL(self._val._replace(query=self._get_str_query(query)), encoded=True) + + def with_fragment(self, fragment): + """Return a new URL with fragment replaced. + + Autoencode fragment if needed. + + Clear fragment to default if None is passed. + + """ + # N.B. doesn't cleanup query/fragment + if fragment is None: + raw_fragment = "" + elif not isinstance(fragment, str): + raise TypeError("Invalid fragment type") + else: + raw_fragment = self._FRAGMENT_QUOTER(fragment) + if self.raw_fragment == raw_fragment: + return self + return URL(self._val._replace(fragment=raw_fragment), encoded=True) + + def with_name(self, name): + """Return a new URL with name (last part of path) replaced. + + Query and fragment parts are cleaned up. + + Name is encoded if needed. + + """ + # N.B. DOES cleanup query/fragment + if not isinstance(name, str): + raise TypeError("Invalid name type") + if "/" in name: + raise ValueError("Slash in name is not allowed") + name = self._PATH_QUOTER(name) + if name in (".", ".."): + raise ValueError(". and .. values are forbidden") + parts = list(self.raw_parts) + if self.is_absolute(): + if len(parts) == 1: + parts.append(name) + else: + parts[-1] = name + parts[0] = "" # replace leading '/' + else: + parts[-1] = name + if parts[0] == "/": + parts[0] = "" # replace leading '/' + return URL( + self._val._replace(path="/".join(parts), query="", fragment=""), + encoded=True, + ) + + def with_suffix(self, suffix): + """Return a new URL with suffix (file extension of name) replaced. + + Query and fragment parts are cleaned up. + + suffix is encoded if needed. + """ + if not isinstance(suffix, str): + raise TypeError("Invalid suffix type") + if suffix and not suffix.startswith(".") or suffix == ".": + raise ValueError(f"Invalid suffix {suffix!r}") + name = self.raw_name + if not name: + raise ValueError(f"{self!r} has an empty name") + old_suffix = self.raw_suffix + if not old_suffix: + name = name + suffix + else: + name = name[: -len(old_suffix)] + suffix + return self.with_name(name) + + def join(self, url): + """Join URLs + + Construct a full (“absolute”) URL by combining a “base URL” + (self) with another URL (url). + + Informally, this uses components of the base URL, in + particular the addressing scheme, the network location and + (part of) the path, to provide missing components in the + relative URL. + + """ + # See docs for urllib.parse.urljoin + if not isinstance(url, URL): + raise TypeError("url should be URL") + return URL(urljoin(str(self), str(url)), encoded=True) + + def human_repr(self): + """Return decoded human readable string for URL representation.""" + user = _human_quote(self.user, "#/:?@") + password = _human_quote(self.password, "#/:?@") + host = self.host + if host: + host = self._encode_host(self.host, human=True) + path = _human_quote(self.path, "#?") + query_string = "&".join( + "{}={}".format(_human_quote(k, "#&+;="), _human_quote(v, "#&+;=")) + for k, v in self.query.items() + ) + fragment = _human_quote(self.fragment, "") + return urlunsplit( + SplitResult( + self.scheme, + self._make_netloc( + user, + password, + host, + self._val.port, + encode_host=False, + ), + path, + query_string, + fragment, + ) + ) + + +def _human_quote(s, unsafe): + if not s: + return s + for c in "%" + unsafe: + if c in s: + s = s.replace(c, f"%{ord(c):02X}") + if s.isprintable(): + return s + return "".join(c if c.isprintable() else quote(c) for c in s) + + +_MAXCACHE = 256 + + +@functools.lru_cache(_MAXCACHE) +def _idna_decode(raw): + try: + return idna.decode(raw.encode("ascii")) + except UnicodeError: # e.g. '::1' + return raw.encode("ascii").decode("idna") + + +@functools.lru_cache(_MAXCACHE) +def _idna_encode(host): + try: + return idna.encode(host, uts46=True).decode("ascii") + except UnicodeError: + return host.encode("idna").decode("ascii") + + +@rewrite_module +def cache_clear(): + _idna_decode.cache_clear() + _idna_encode.cache_clear() + + +@rewrite_module +def cache_info(): + return { + "idna_encode": _idna_encode.cache_info(), + "idna_decode": _idna_decode.cache_info(), + } + + +@rewrite_module +def cache_configure(*, idna_encode_size=_MAXCACHE, idna_decode_size=_MAXCACHE): + global _idna_decode, _idna_encode + + _idna_encode = functools.lru_cache(idna_encode_size)(_idna_encode.__wrapped__) + _idna_decode = functools.lru_cache(idna_decode_size)(_idna_decode.__wrapped__) diff --git a/lib/yarl/py.typed b/lib/yarl/py.typed new file mode 100644 index 0000000..dcf2c80 --- /dev/null +++ b/lib/yarl/py.typed @@ -0,0 +1 @@ +# Placeholder diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a73b338 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +openai==0.27.2 \ No newline at end of file

  • {name}