diff --git a/.gitattributes b/.gitattributes index b8eeea8b549d0f547eb5eeeb27c09713e154b507..a54db4423d2f51652d7c64712f7a7d7074f44fbf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -285,3 +285,13 @@ hug/lib/python3.12/site-packages/pip/_vendor/distlib/w64.exe filter=lfs diff=lfs hug/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text hug/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text hug/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/polars/__pycache__/selectors.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/polars/dataframe/__pycache__/frame.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/polars/expr/__pycache__/datetime.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/polars/expr/__pycache__/expr.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/polars/expr/__pycache__/string.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/polars/lazyframe/__pycache__/frame.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/polars/series/__pycache__/series.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/psutil/_psutil_linux.abi3.so filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/pyparsing/__pycache__/core.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text +hug/lib/python3.12/site-packages/setuptools/_vendor/backports/tarfile/__pycache__/__init__.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/INSTALLER b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/METADATA b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..bcbbe18153ba45f0338d00afb311feeaf10d95df --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/METADATA @@ -0,0 +1,110 @@ +Metadata-Version: 2.4 +Name: pip +Version: 26.2.2 +Summary: The PyPA recommended tool for installing Python packages. +Author-email: The pip developers +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-Expression: MIT +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Build Tools +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +License-File: AUTHORS.txt +License-File: LICENSE.txt +License-File: src/pip/_vendor/cachecontrol/LICENSE.txt +License-File: src/pip/_vendor/certifi/LICENSE +License-File: src/pip/_vendor/distlib/LICENSE.txt +License-File: src/pip/_vendor/distro/LICENSE +License-File: src/pip/_vendor/idna/LICENSE.md +License-File: src/pip/_vendor/msgpack/COPYING +License-File: src/pip/_vendor/packaging/LICENSE +License-File: src/pip/_vendor/packaging/LICENSE.APACHE +License-File: src/pip/_vendor/packaging/LICENSE.BSD +License-File: src/pip/_vendor/pkg_resources/LICENSE +License-File: src/pip/_vendor/platformdirs/LICENSE +License-File: src/pip/_vendor/pygments/LICENSE +License-File: src/pip/_vendor/pyproject_hooks/LICENSE +License-File: src/pip/_vendor/requests/LICENSE +License-File: src/pip/_vendor/resolvelib/LICENSE +License-File: src/pip/_vendor/rich/LICENSE +License-File: src/pip/_vendor/tomli/LICENSE +License-File: src/pip/_vendor/tomli_w/LICENSE +License-File: src/pip/_vendor/truststore/LICENSE +License-File: src/pip/_vendor/urllib3/LICENSE.txt +Project-URL: Changelog, https://pip.pypa.io/en/stable/news/ +Project-URL: Documentation, https://pip.pypa.io +Project-URL: Homepage, https://pip.pypa.io/ +Project-URL: Source, https://github.com/pypa/pip + +pip - The Python Package Installer +================================== + +.. |pypi-version| image:: https://img.shields.io/pypi/v/pip.svg + :target: https://pypi.org/project/pip/ + :alt: PyPI + +.. |python-versions| image:: https://img.shields.io/pypi/pyversions/pip + :target: https://pypi.org/project/pip + :alt: PyPI - Python Version + +.. |docs-badge| image:: https://readthedocs.org/projects/pip/badge/?version=latest + :target: https://pip.pypa.io/en/latest + :alt: Documentation + +|pypi-version| |python-versions| |docs-badge| + +Pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes. + +Please take a look at our documentation for how to install and use pip: + +* `Installation`_ +* `Usage`_ + +We release updates regularly, with a new version every 3 months. Find more details in our documentation: + +* `Release notes`_ +* `Release process`_ + +If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms: + +* `Issue tracking`_ +* `Discourse channel`_ +* `User IRC`_ + +If you want to get involved, head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms: + +* `GitHub page`_ +* `Development documentation`_ +* `Development IRC`_ + +Code of Conduct +--------------- + +Everyone interacting in the pip project's codebases, issue trackers, chat +rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_. + +.. _package installer: https://packaging.python.org/guides/tool-recommendations/ +.. _Python Package Index: https://pypi.org +.. _Installation: https://pip.pypa.io/en/stable/installation/ +.. _Usage: https://pip.pypa.io/en/stable/ +.. _Release notes: https://pip.pypa.io/en/stable/news.html +.. _Release process: https://pip.pypa.io/en/latest/development/release-process/ +.. _GitHub page: https://github.com/pypa/pip +.. _Development documentation: https://pip.pypa.io/en/latest/development +.. _Issue tracking: https://github.com/pypa/pip/issues +.. _Discourse channel: https://discuss.python.org/c/packaging +.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa +.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev +.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md + diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/RECORD b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..f95948aa2ab79bfcaebf31854e84c043544dba11 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/RECORD @@ -0,0 +1,889 @@ +../../../bin/pip,sha256=ysZ9OBR2eaNzAuvIShE0iQBEb8DYK2wMkfZmxOEU3eU,244 +../../../bin/pip3,sha256=ysZ9OBR2eaNzAuvIShE0iQBEb8DYK2wMkfZmxOEU3eU,244 +../../../bin/pip3.12,sha256=ysZ9OBR2eaNzAuvIShE0iQBEb8DYK2wMkfZmxOEU3eU,244 +pip-26.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pip-26.2.2.dist-info/METADATA,sha256=_hV6YKlwsoh3D615oSQAH1OKMlsKInDFlHkrakdet5g,4617 +pip-26.2.2.dist-info/RECORD,, +pip-26.2.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip-26.2.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +pip-26.2.2.dist-info/entry_points.txt,sha256=Vhf8s0IYgX37mtd4vGL73BPcxdKnqeCFPzB5-d30x8o,84 +pip-26.2.2.dist-info/licenses/AUTHORS.txt,sha256=TrH7FwXi3viY6BvLoZw6rIBiDSe6NWnWpt97RinRtcI,12139 +pip-26.2.2.dist-info/licenses/LICENSE.txt,sha256=Y0MApmnUmurmWxLGxIySTFGkzfPR_whtw0VtyLyqIQQ,1093 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt,sha256=hu7uh74qQ_P_H1ZJb0UfaSQ5JvAl_tuwM2ZsMExMFhs,558 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/certifi/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/distlib/LICENSE.txt,sha256=gI4QyKarjesUn_mz-xn0R6gICUYG1xKpylf-rTVSWZ0,14531 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/distro/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md,sha256=GppPDj1HmickDd1ZqRN6ZqtKD539yMphiMwL_YUYfwQ,1541 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/msgpack/COPYING,sha256=SS3tuoXaWHL3jmCRvNH-pHTWYNNay03ulkuKqz8AdCc,614 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE,sha256=KeD9YukphQ6G6yjD_czwzv30-pSHkBHP-z0NS-1tTbY,1089 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/pygments/LICENSE,sha256=qdZvHVJt8C4p3Oc0NtNOVuhjL0bCdbvf_HBWnogvnxc,1331 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE,sha256=GyKwSbUmfW38I6Z79KhNjsBLn9-xpR02DkK0NCyLQVQ,1081 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/requests/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE,sha256=84j9OMrRMRLB3A9mm76A5_hFQe26-3LzAw0sp2QsPJ0,751 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/rich/LICENSE,sha256=3u18F6QxgVgZCj6iOcyHmlpQJxzruYrnAl9I--WNyhU,1056 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE,sha256=uAgWsNUwuKzLTCIReDeQmEpuO2GSLCte6S8zcqsnQv4,1072 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE,sha256=uAgWsNUwuKzLTCIReDeQmEpuO2GSLCte6S8zcqsnQv4,1072 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/truststore/LICENSE,sha256=M757fo-k_Rmxdg4ajtimaL2rhSyRtpLdQUJLy3Jan8o,1086 +pip-26.2.2.dist-info/licenses/src/pip/_vendor/urllib3/LICENSE.txt,sha256=Ew46ZNX91dCWp1JpRjSn2d8oRGnehuVzIQAmgEHj1oY,1093 +pip/__init__.py,sha256=S6PbvnKOClOi1dqd_bH9_j2wUOSXjtZn6h_5tKBoHNg,355 +pip/__main__.py,sha256=rOZRtrXjDBzY24niaxTnd9ZHHWL7B0EawVdLoJ3nI6c,874 +pip/__pip-runner__.py,sha256=720Mt6h07Uce52v80EOF__JYauoLw9b7Pfs_5B91isg,1451 +pip/__pycache__/__init__.cpython-312.pyc,, +pip/__pycache__/__main__.cpython-312.pyc,, +pip/__pycache__/__pip-runner__.cpython-312.pyc,, +pip/_internal/__init__.py,sha256=S7i9Dn9aSZS0MG-2Wrve3dV9TImPzvQn5jjhp9t_uf0,511 +pip/_internal/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/__pycache__/cache.cpython-312.pyc,, +pip/_internal/__pycache__/configuration.cpython-312.pyc,, +pip/_internal/__pycache__/exceptions.cpython-312.pyc,, +pip/_internal/__pycache__/main.cpython-312.pyc,, +pip/_internal/__pycache__/pyproject.cpython-312.pyc,, +pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc,, +pip/_internal/__pycache__/wheel_builder.cpython-312.pyc,, +pip/_internal/build_env/__init__.py,sha256=AI5x2Kfv1exG1IbsNHuIV1agqNgstTj_LWpbwWLq8ok,788 +pip/_internal/build_env/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/build_env/__pycache__/base.cpython-312.pyc,, +pip/_internal/build_env/__pycache__/installer.cpython-312.pyc,, +pip/_internal/build_env/__pycache__/noop.cpython-312.pyc,, +pip/_internal/build_env/__pycache__/venv.cpython-312.pyc,, +pip/_internal/build_env/__pycache__/virtual.cpython-312.pyc,, +pip/_internal/build_env/base.py,sha256=7RHW5X-QSVWHeK7_5Uei9Gbq0q1pAXHF9tq7Mj3wtTs,3440 +pip/_internal/build_env/installer.py,sha256=eMUytQCdBiQ9TgkaGeTAzq_IVBqLHyD9OJ_kNTIjjcM,13046 +pip/_internal/build_env/noop.py,sha256=FXk9ZFTTN0s1cecZE6ycN2ZznBh75Ok2I3QXF8HTo6I,1001 +pip/_internal/build_env/venv.py,sha256=PsDl0PpmB9jRNaIawhy6Xk9R8WcYoAogLS2afmZ9AQc,5692 +pip/_internal/build_env/virtual.py,sha256=HsVRzzhgBGyy06DZBoO0PW_3kIEacy9S4TI5H1q72Hk,4858 +pip/_internal/cache.py,sha256=eqMQVfpD8x7GcCO0V0z73mnjPIuSapMFoQ_WRE7iLY8,10457 +pip/_internal/cli/__init__.py,sha256=Iqg_tKA771XuMO1P4t_sDHnSKPzkUb9D0DqunAmw_ko,131 +pip/_internal/cli/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc,, +pip/_internal/cli/__pycache__/base_command.cpython-312.pyc,, +pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc,, +pip/_internal/cli/__pycache__/command_context.cpython-312.pyc,, +pip/_internal/cli/__pycache__/index_command.cpython-312.pyc,, +pip/_internal/cli/__pycache__/main.cpython-312.pyc,, +pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc,, +pip/_internal/cli/__pycache__/parser.cpython-312.pyc,, +pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc,, +pip/_internal/cli/__pycache__/req_command.cpython-312.pyc,, +pip/_internal/cli/__pycache__/spinners.cpython-312.pyc,, +pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc,, +pip/_internal/cli/autocompletion.py,sha256=gFtmiFhHccizyx4JPJnEKik-uftU7kwrUvm72cSFPHI,7353 +pip/_internal/cli/base_command.py,sha256=yF5_mQEIrLG2G0nvymZS0nLhiDg4B0T89W5iX78H558,9431 +pip/_internal/cli/cmdoptions.py,sha256=aBNxaIy4YV4AfjAqrG8RoZFcv7kwWzRAzg5mnZmOGDw,41180 +pip/_internal/cli/command_context.py,sha256=kmu3EWZbfBega1oDamnGJTA_UaejhIQNuMj2CVmMXu0,817 +pip/_internal/cli/index_command.py,sha256=_Qzc9oHnButVvD0wxYcHfe0uJZqRWOtIg5HaQpnSNeM,7504 +pip/_internal/cli/main.py,sha256=O10guzBqF2MykAaZYo6sua097dXqC3nozKodtTOpHDc,3209 +pip/_internal/cli/main_parser.py,sha256=7PhEfZ8AXiWUpTfB_KJMngI2sF8XIXl52alI1LZ_e_M,4368 +pip/_internal/cli/parser.py,sha256=WvRHuI2MdFp187BWrY_D17L02asq9jmCnng81af52SQ,14147 +pip/_internal/cli/progress_bars.py,sha256=13U3AY3DYwdEIwPYLtUn5B4QcpJUApLWCkfJ-u_PqzY,4706 +pip/_internal/cli/req_command.py,sha256=wW-mRcPqPczeqxp9SQ_yh3YfrRAYAlEAZextlLcDbUc,18952 +pip/_internal/cli/spinners.py,sha256=EJzZIZNyUtJljp3-WjcsyIrqxW-HUsfWzhuW84n_Tqw,7362 +pip/_internal/cli/status_codes.py,sha256=9QhNe6hHqsiw5y5wufiNKBOgJjV0HF3I3P2K21nj-OM,136 +pip/_internal/commands/__init__.py,sha256=aNeCbQurGWihfhQq7BqaLXHqWDQ0i3I04OS7kxK6plQ,4026 +pip/_internal/commands/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/commands/__pycache__/cache.cpython-312.pyc,, +pip/_internal/commands/__pycache__/check.cpython-312.pyc,, +pip/_internal/commands/__pycache__/completion.cpython-312.pyc,, +pip/_internal/commands/__pycache__/configuration.cpython-312.pyc,, +pip/_internal/commands/__pycache__/debug.cpython-312.pyc,, +pip/_internal/commands/__pycache__/download.cpython-312.pyc,, +pip/_internal/commands/__pycache__/freeze.cpython-312.pyc,, +pip/_internal/commands/__pycache__/hash.cpython-312.pyc,, +pip/_internal/commands/__pycache__/help.cpython-312.pyc,, +pip/_internal/commands/__pycache__/index.cpython-312.pyc,, +pip/_internal/commands/__pycache__/inspect.cpython-312.pyc,, +pip/_internal/commands/__pycache__/install.cpython-312.pyc,, +pip/_internal/commands/__pycache__/list.cpython-312.pyc,, +pip/_internal/commands/__pycache__/lock.cpython-312.pyc,, +pip/_internal/commands/__pycache__/search.cpython-312.pyc,, +pip/_internal/commands/__pycache__/show.cpython-312.pyc,, +pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc,, +pip/_internal/commands/__pycache__/wheel.cpython-312.pyc,, +pip/_internal/commands/cache.py,sha256=YLZrLWNvChl0pImBkoSZGGD3fb2MfdYGpYF4J0aQs6w,9251 +pip/_internal/commands/check.py,sha256=hVFBQezQ3zj4EydoWbFQj_afPUppMt7r9JPAlY22U6Y,2244 +pip/_internal/commands/completion.py,sha256=LjvRIZ6QUiDXJL3IOMFeD-_J97HfjMGgEk0j2tWGu1U,4565 +pip/_internal/commands/configuration.py,sha256=5M6KM30G3i6oMHAsVUSWP4E93cSSJQoJ6evURC754E8,10132 +pip/_internal/commands/debug.py,sha256=tCWUOEyz2HdlLGTvzNqSZIAJQkQYCy58zBTdfIWyzSo,6556 +pip/_internal/commands/download.py,sha256=cuXEj9PmoZ4kCsxm_BaZ_OkmIsV_gluLiOVSuOO2MS0,5404 +pip/_internal/commands/freeze.py,sha256=Lg8EOokxnRZrwdLWJgLpBWxNUt7-HrkWlEC05e-vBww,3100 +pip/_internal/commands/hash.py,sha256=GO9pRN3wXC2kQaovK57TaLYBMc3IltOH92O6QEw6YE0,1679 +pip/_internal/commands/help.py,sha256=Bz3LcjNQXkz4Cu__pL4CZ86o4-HNLZj1NZWdlJhjuu0,1108 +pip/_internal/commands/index.py,sha256=ny_hGzXuZHzjsq4CIJh_6YfZGVJm7G7LqQQdKTibJ_Q,5608 +pip/_internal/commands/inspect.py,sha256=iiAM-JuL_Z1-QP34cgAloZPSr50Mc7wAvuFDRzHQrQk,3185 +pip/_internal/commands/install.py,sha256=DREULA-TGbCKhqBBLkrbDKztyQ557QbDe6ZJUYuh2yk,33761 +pip/_internal/commands/list.py,sha256=W-FyxXTrHJhqRl4ZoqFp68_5GAwC9GYuY6fhFTJdHzU,13908 +pip/_internal/commands/lock.py,sha256=VhpNRWItLdqywSOIqXg3vuIyf59cozh09-Z6Z9cMiLM,5987 +pip/_internal/commands/search.py,sha256=zbMsX_YASj6kXA6XIBgTDv0bGK51xG-CV3IynZJcE-c,5782 +pip/_internal/commands/show.py,sha256=91_XljhCHWyCNfoFBa4XdacbumQgoNbhkcHHk_gcW9Y,8247 +pip/_internal/commands/uninstall.py,sha256=CsOihqvb6ZA6O67L70oXeoLHeOfNzMM88H9g-9aocgw,3868 +pip/_internal/commands/wheel.py,sha256=5m3NvVKatqLDOVoC_4WOFiDDrvQRjB52jkbvpbZApug,6187 +pip/_internal/configuration.py,sha256=0eEdh1xTlOHhoCrTtwHYM5DEj7BbTmhIaCaNNj3srBM,14562 +pip/_internal/distributions/__init__.py,sha256=Hq6kt6gXBgjNit5hTTWLAzeCNOKoB-N0pGYSqehrli8,858 +pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/distributions/__pycache__/base.cpython-312.pyc,, +pip/_internal/distributions/__pycache__/installed.cpython-312.pyc,, +pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc,, +pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc,, +pip/_internal/distributions/base.py,sha256=BjYazg2EhbEgrO1W_JD6jEZIDVlUpA_WZkqU-NvCBkQ,1907 +pip/_internal/distributions/installed.py,sha256=QPDhCqc-GTGiWZAAOFaVKMETg1m_kYuf6RBO9ZZlDqY,994 +pip/_internal/distributions/sdist.py,sha256=hof85VbuXa_NydvFZj6zOtnxuPY15tsuCJVirLuNT-U,7783 +pip/_internal/distributions/wheel.py,sha256=qOMVYzKT3tJsO0QundLjADcFiW_RODui4K51XRqMru8,1429 +pip/_internal/exceptions.py,sha256=Zdu17pVbD1eI_yjg52Zd0ygAWDy8LtXS1Atfvn1pPts,38302 +pip/_internal/index/__init__.py,sha256=tzwMH_fhQeubwMqHdSivasg1cRgTSbNg2CiMVnzMmyU,29 +pip/_internal/index/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/index/__pycache__/collector.cpython-312.pyc,, +pip/_internal/index/__pycache__/package_finder.cpython-312.pyc,, +pip/_internal/index/__pycache__/sources.cpython-312.pyc,, +pip/_internal/index/collector.py,sha256=bu-NBVToIeyYEbcv0_gtInAxsCK3E0yI6Je6yxAwE5k,16578 +pip/_internal/index/package_finder.py,sha256=RoWK5rPkOcmOdiWEXhnk9aaFDgaj4QEE70FeQokqZf4,43149 +pip/_internal/index/sources.py,sha256=fqqVli93Lr-HNk-wFD67yyfZzS6WeDkSZGv1RFw63QE,8621 +pip/_internal/locations/__init__.py,sha256=iP9yVZn_4iPuNcaUNh2A_vJLcvUZ-8y4zvKLDVEqaM0,14022 +pip/_internal/locations/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc,, +pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc,, +pip/_internal/locations/__pycache__/base.cpython-312.pyc,, +pip/_internal/locations/_distutils.py,sha256=jpFj4V00rD9IR3vA9TqrGkwcdNVFc58LsChZavge9JY,5975 +pip/_internal/locations/_sysconfig.py,sha256=HoZYQ1sVQD3IRpQCp4TpdTQLNrDFFzI9zRjXrTYnJWY,7788 +pip/_internal/locations/base.py,sha256=5mVcXh9jnSepEO25aG2U-qZWguzeoOPIbT0mRiBDDME,2548 +pip/_internal/main.py,sha256=1cHqjsfFCrMFf3B5twzocxTJUdHMLoXUpy5lJoFqUi8,338 +pip/_internal/metadata/__init__.py,sha256=vp-JAxiWg_-l5F8AT0Jcey72uUnh8CDwwol9-KktHZ8,5824 +pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/metadata/__pycache__/_json.cpython-312.pyc,, +pip/_internal/metadata/__pycache__/base.cpython-312.pyc,, +pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc,, +pip/_internal/metadata/_json.py,sha256=hNvnMHOXLAyNlzirWhPL9Nx2CvCqa1iRma6Osq1YfV8,2711 +pip/_internal/metadata/base.py,sha256=_o9lMyHmia4Ea9fhceyB9rp3jUUU8k7FmlhXB7N9_tM,25642 +pip/_internal/metadata/importlib/__init__.py,sha256=jUUidoxnHcfITHHaAWG1G2i5fdBYklv_uJcjo2x7VYE,135 +pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc,, +pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc,, +pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc,, +pip/_internal/metadata/importlib/_compat.py,sha256=sneVh4_6WxQZK4ljdl3ylVuP-q0ttSqbgl9mWt0HnOg,2804 +pip/_internal/metadata/importlib/_dists.py,sha256=akX5xiLB_3dvbeb2gHL8i7wORi01_Cnty0HnKFQAF0Y,8717 +pip/_internal/metadata/importlib/_envs.py,sha256=ulCRDEXYeinwS5AXL-Quj7C4Erk20kHhIwBmE2P3BYA,5590 +pip/_internal/metadata/pkg_resources.py,sha256=NO76ZrfR2-LKJTyaXrmQoGhmJMArALvacrlZHViSDT8,10544 +pip/_internal/models/__init__.py,sha256=AjmCEBxX_MH9f_jVjIGNCFJKYCYeSEe18yyvNx4uRKQ,62 +pip/_internal/models/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/models/__pycache__/candidate.cpython-312.pyc,, +pip/_internal/models/__pycache__/direct_url.cpython-312.pyc,, +pip/_internal/models/__pycache__/format_control.cpython-312.pyc,, +pip/_internal/models/__pycache__/index.cpython-312.pyc,, +pip/_internal/models/__pycache__/installation_report.cpython-312.pyc,, +pip/_internal/models/__pycache__/link.cpython-312.pyc,, +pip/_internal/models/__pycache__/release_control.cpython-312.pyc,, +pip/_internal/models/__pycache__/scheme.cpython-312.pyc,, +pip/_internal/models/__pycache__/search_scope.cpython-312.pyc,, +pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc,, +pip/_internal/models/__pycache__/target_python.cpython-312.pyc,, +pip/_internal/models/__pycache__/wheel.cpython-312.pyc,, +pip/_internal/models/candidate.py,sha256=MvANZJiHkw6Y4T-oYSM8710G6yYB4_DwofoD7BAmrnM,824 +pip/_internal/models/direct_url.py,sha256=9RS3TQAXknwLd8JOOZWlbBV6WZFs9qn-YAYk0VQ8R_Q,944 +pip/_internal/models/format_control.py,sha256=PwemYG1L27BM0f1KP61rm24wShENFyxqlD1TWu34alc,2471 +pip/_internal/models/index.py,sha256=tYnL8oxGi4aSNWur0mG8DAP7rC6yuha_MwJO8xw0crI,1030 +pip/_internal/models/installation_report.py,sha256=U4MlXWFB-8ev_yheuMO9T2m_y5b4C-hOzVaoHVobl38,2846 +pip/_internal/models/link.py,sha256=SSXtFO3YAreUYY2R0PqtKXbAe1vV9Bqt5r_qPN218zE,23474 +pip/_internal/models/release_control.py,sha256=31Jh-ZHsTIBZLe-7uPNoYLiRSWXAjEI1jGNxwqLKd4A,3365 +pip/_internal/models/scheme.py,sha256=G-O9KElabcXqbPwfE_66lzX5fGahh3Gu6DAkz_9ZhJw,558 +pip/_internal/models/search_scope.py,sha256=_i-Gj_w_FwZAvTs7WhjslBDD70J-8hmIdjXByI8uEZQ,4461 +pip/_internal/models/selection_prefs.py,sha256=0teekwSVxW5MOk0WPG5Novw5q_XJjxijcd75kZO6E9g,1503 +pip/_internal/models/target_python.py,sha256=I0eFS-eia3kwhrOvgsphFZtNAB2IwXZ9Sr9fp6IjBP4,4243 +pip/_internal/models/wheel.py,sha256=1SdfDvN7ALTsbyZ9EOsNy1GPirP1n6EjHyzPrZyLSh8,2920 +pip/_internal/network/__init__.py,sha256=FMy06P__y6jMjUc8z3ZcQdKF-pmZ2zM14_vBeHPGhUI,49 +pip/_internal/network/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/network/__pycache__/auth.cpython-312.pyc,, +pip/_internal/network/__pycache__/cache.cpython-312.pyc,, +pip/_internal/network/__pycache__/download.cpython-312.pyc,, +pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc,, +pip/_internal/network/__pycache__/session.cpython-312.pyc,, +pip/_internal/network/__pycache__/utils.cpython-312.pyc,, +pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc,, +pip/_internal/network/auth.py,sha256=TBpwe7zcaPuBdBldjAOZhf20lmkacpIgGA-AqLcr8eU,21545 +pip/_internal/network/cache.py,sha256=6DlMXJ7pb35M1vgtTK0-L_kiHJFxf7t9zrfeJ9vqO34,4862 +pip/_internal/network/download.py,sha256=SVrmKao567lfs8RaCdyuOitzIRCZp9Rf3RcDj_o8vkg,14618 +pip/_internal/network/lazy_wheel.py,sha256=y9gVksdJCSjnLfYzs_m3DYUAtl3hc_k-xFPDBd9DgOs,7646 +pip/_internal/network/session.py,sha256=2KqMv5Hu_PFRYRkDBhSjIbqbsBp0lXv4tqoLKnM2K6o,19924 +pip/_internal/network/utils.py,sha256=OTA1vwFtu2Ee1zWrqLysBpRQGTjvXxCJJUTEqy7hNcA,8128 +pip/_internal/network/xmlrpc.py,sha256=_-Rnk3vOff8uF9hAGmT6SLALflY1gMBcbGwS12fb_Y4,1830 +pip/_internal/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/operations/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/operations/__pycache__/check.cpython-312.pyc,, +pip/_internal/operations/__pycache__/freeze.cpython-312.pyc,, +pip/_internal/operations/__pycache__/prepare.cpython-312.pyc,, +pip/_internal/operations/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/operations/build/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/operations/build/__pycache__/build_tracker.cpython-312.pyc,, +pip/_internal/operations/build/__pycache__/metadata.cpython-312.pyc,, +pip/_internal/operations/build/__pycache__/metadata_editable.cpython-312.pyc,, +pip/_internal/operations/build/__pycache__/wheel.cpython-312.pyc,, +pip/_internal/operations/build/__pycache__/wheel_editable.cpython-312.pyc,, +pip/_internal/operations/build/build_tracker.py,sha256=W3b5cmkMWPaE6QIwfzsTayJo7-OlxFHWDxfPuax1KcE,4771 +pip/_internal/operations/build/metadata.py,sha256=INHaeiRfOiLYCXApfDNRo9Cw2xI4VwTc0KItvfdfOjk,1421 +pip/_internal/operations/build/metadata_editable.py,sha256=oWudMsnjy4loO_Jy7g4N9nxsnaEX_iDlVRgCy7pu1rs,1509 +pip/_internal/operations/build/wheel.py,sha256=3bP-nNiJ4S8JvMaBnyessXQUBhxTqt1GBx6DQ1iPJDY,1136 +pip/_internal/operations/build/wheel_editable.py,sha256=q3kfElclM6FutVbFwE87JOTpVWt5ixDf3_UkHAIVfz4,1478 +pip/_internal/operations/check.py,sha256=k7mTjLb5yZbidYw8Nt9vu7_u1L1ekLOzUECeN4qcNsA,5891 +pip/_internal/operations/freeze.py,sha256=PDdY-y_ZtZZJLAKcaWPIGRKAGW7DXR48f0aMRU0j7BA,9854 +pip/_internal/operations/install/__init__.py,sha256=ak-UETcQPKlFZaWoYKWu5QVXbpFBvg0sXc3i0O4vSYY,50 +pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc,, +pip/_internal/operations/install/wheel.py,sha256=qhFwJSShLm4quY8zIss_t-bff0YV49HTNAKfu4fSpic,29078 +pip/_internal/operations/prepare.py,sha256=zLkVMSDu9cUR0KnDm3bZ3QvIeaErhJKrr109UyDMlKQ,35030 +pip/_internal/pyproject.py,sha256=J-sTWqC-XfsKQgz9m1bypMWZPHItsSHzIN_NWeIRmhM,4555 +pip/_internal/req/__init__.py,sha256=x3uB7ZW-l_MOPm8ldF8Gi0dh5C8W23dB_wOH2NmWpFc,3140 +pip/_internal/req/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/req/__pycache__/constructors.cpython-312.pyc,, +pip/_internal/req/__pycache__/pep723.cpython-312.pyc,, +pip/_internal/req/__pycache__/req_dependency_group.cpython-312.pyc,, +pip/_internal/req/__pycache__/req_file.cpython-312.pyc,, +pip/_internal/req/__pycache__/req_install.cpython-312.pyc,, +pip/_internal/req/__pycache__/req_set.cpython-312.pyc,, +pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc,, +pip/_internal/req/constructors.py,sha256=fwILg3OiIK_f13RUGX5zQyz4YasUMp7MC6u9uPvoGzw,22276 +pip/_internal/req/pep723.py,sha256=JsG1p3CaVcW8cjclD2kDj7d9qtVrNQaevY_UnKh00tk,1242 +pip/_internal/req/req_dependency_group.py,sha256=PrWKtlwI8xbWnWEKjXs9RkrpQx2_h02ABefYT0ZdeQg,3145 +pip/_internal/req/req_file.py,sha256=dmGBSxZ9dj1eOq2NXv94bor0u1n8P4-AvmJL-vYzIsE,20644 +pip/_internal/req/req_install.py,sha256=r289__WNVaTxpRtLFUz3FpNvdGv8yYa87G-QkP5DsJc,32173 +pip/_internal/req/req_set.py,sha256=awkqIXnYA4Prmsj0Qb3zhqdbYUmXd-1o0P-KZ3mvRQs,2828 +pip/_internal/req/req_uninstall.py,sha256=t5HSYuLoLxnF7jrcIBDjTEP-sjjr8O4fZouEqOkVM_c,24276 +pip/_internal/resolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/resolution/__pycache__/base.cpython-312.pyc,, +pip/_internal/resolution/base.py,sha256=J9Fq3kFOjFj9gBShYQtldEFPrRGSWtsp-rO9rIc4W4g,573 +pip/_internal/resolution/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc,, +pip/_internal/resolution/legacy/resolver.py,sha256=2CVLIZLm_nXkfJTt5RKtIbIxvCkn9Gavf4Yp9c1IjDU,24110 +pip/_internal/resolution/resolvelib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc,, +pip/_internal/resolution/resolvelib/base.py,sha256=fo7ojoQtgjmQH5ca3VyKJAO3p5sHnihMDw79Z8_31-U,5900 +pip/_internal/resolution/resolvelib/candidates.py,sha256=SEkpEH6T6zl7Ve7Dw9oIS6tnREj-1U_q4U-2xEH0ISc,20848 +pip/_internal/resolution/resolvelib/factory.py,sha256=rqwnD4GWEdVgG7nGP6OGj493yiGW6jZr0cW_msMOK_U,36162 +pip/_internal/resolution/resolvelib/found_candidates.py,sha256=F55YtXekKUbVwphlXska01LkdTGVgx1qONlLK7BX-HM,6005 +pip/_internal/resolution/resolvelib/provider.py,sha256=Nkx4A16MOAxjCj9jFm8wmlMk5cbWh1YK2-UYWTKEJCw,12310 +pip/_internal/resolution/resolvelib/reporter.py,sha256=tEC7MF8IqGU4Ww9t61YVfNFKtoaQpb6-AwLZptJz1VE,3918 +pip/_internal/resolution/resolvelib/requirements.py,sha256=Izl9n8nc188lA1BSPS8QxfudfDQPHgngw-ij6hXt0nQ,8239 +pip/_internal/resolution/resolvelib/resolver.py,sha256=wM3CcRTYPdjnQ1ai3YP6htFmkE32o1xx2RgjU-ykAjc,13849 +pip/_internal/self_outdated_check.py,sha256=9XxOXPqsZlKceTP2tZS7I7rXjodnqd3GrIQ8U0_L8BM,8097 +pip/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/utils/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc,, +pip/_internal/utils/__pycache__/_log.cpython-312.pyc,, +pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc,, +pip/_internal/utils/__pycache__/compat.cpython-312.pyc,, +pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc,, +pip/_internal/utils/__pycache__/datetime.cpython-312.pyc,, +pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc,, +pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc,, +pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc,, +pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc,, +pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc,, +pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc,, +pip/_internal/utils/__pycache__/glibc.cpython-312.pyc,, +pip/_internal/utils/__pycache__/hashes.cpython-312.pyc,, +pip/_internal/utils/__pycache__/logging.cpython-312.pyc,, +pip/_internal/utils/__pycache__/misc.cpython-312.pyc,, +pip/_internal/utils/__pycache__/packaging.cpython-312.pyc,, +pip/_internal/utils/__pycache__/pylock.cpython-312.pyc,, +pip/_internal/utils/__pycache__/retry.cpython-312.pyc,, +pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc,, +pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc,, +pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc,, +pip/_internal/utils/__pycache__/urls.cpython-312.pyc,, +pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc,, +pip/_internal/utils/__pycache__/wheel.cpython-312.pyc,, +pip/_internal/utils/_jaraco_text.py,sha256=M15uUPIh5NpP1tdUGBxRau6q1ZAEtI8-XyLEETscFfE,3350 +pip/_internal/utils/_log.py,sha256=-jHLOE_THaZz5BFcCnoSL9EYAtJ0nXem49s9of4jvKw,1015 +pip/_internal/utils/appdirs.py,sha256=LrzDPZMKVh0rubtCx9vu3XlZbLCSug6VSj4Qsvt66BA,1681 +pip/_internal/utils/compat.py,sha256=i2JUZXGrqP-ZoD8t4gwckBA3KYNUWCluvRDXScts5fE,2601 +pip/_internal/utils/compatibility_tags.py,sha256=DiNSLqpuruXUamGQwOJ2WZByDGLTGaXi9O-Xf8fOi34,6630 +pip/_internal/utils/datetime.py,sha256=kuJOf1mW8G5tRFN6jWardddS-9qSaR53lK1jmx3NTZY,868 +pip/_internal/utils/deprecation.py,sha256=IQFumZa-XWJ3UoPnX4duwbf79CTzOZD_Vk1PkivAu4g,4537 +pip/_internal/utils/direct_url_helpers.py,sha256=WCCPJnmoPHz7kiYePrEcVAen2lDpCT9WQVImoHHWCO8,3363 +pip/_internal/utils/egg_link.py,sha256=YWfsrbmfcrfWgqQYy6OuIjsyb9IfL1q_2v4zsms1WjI,2459 +pip/_internal/utils/entrypoints.py,sha256=uPjAyShKObdotjQjJUzprQ6r3xQvDIZwUYfHHqZ7Dok,3324 +pip/_internal/utils/filesystem.py,sha256=gig73bOOS1NnDB94EDDvdDnHjQoeRz_TlfQPiCV7Me0,6812 +pip/_internal/utils/filetypes.py,sha256=sEMa38qaqjvx1Zid3OCAUja31BOBU-USuSMPBvU3yjo,689 +pip/_internal/utils/glibc.py,sha256=sEh8RJJLYSdRvTqAO4THVPPA-YSDVLD4SI9So-bxX1U,3726 +pip/_internal/utils/hashes.py,sha256=38-bCOJSHippQ7r9RttrMHxb2mv3EARt1Gw8kFmW73g,5040 +pip/_internal/utils/logging.py,sha256=hGTjBuIjNZxydV6vs4woxAbhkHI8L-9uCO14OCvPlA8,13919 +pip/_internal/utils/misc.py,sha256=28LWQFvFWmrpJqMcV0soif1xnl3_gb25B3gG_iU5zhM,25408 +pip/_internal/utils/packaging.py,sha256=s5tpUmFumwV0H9JSTzryrIY4JwQM8paGt7Sm7eNwt2Y,1601 +pip/_internal/utils/pylock.py,sha256=XQTZ1ggh7yfpHfRdbvjSAe1peOtg2Z2xFrUeZ7wl4Ts,10290 +pip/_internal/utils/retry.py,sha256=oOK3VGgKRKxSkXDPARf73cnQS3hDtnE9-feAg4AlNZE,1488 +pip/_internal/utils/subprocess.py,sha256=nqNz2p3G4QANCCBcaJkqIQvQUYA50TwjZnoYjiyPxs8,9001 +pip/_internal/utils/temp_dir.py,sha256=TDiL9BzEp2eHalQLH0rhyM3m2xuydEg9c6t1h4bT5l8,9303 +pip/_internal/utils/unpacking.py,sha256=5i9Sn__SNElhoCXnLt2SvQaQ-SnTzcUaDH1_JI1THUw,14789 +pip/_internal/utils/urls.py,sha256=JQWrRAoXHBpPsY9Aij61bsZjMQLCcuyrUO1R6W_BwIo,1647 +pip/_internal/utils/virtualenv.py,sha256=9od4LsceDynUSsLTEXXgtumlUQ8VvKp86b2xzK0Yu3g,2272 +pip/_internal/utils/wheel.py,sha256=YdRuj6MicG-Q9Mg03FbUv1WTLam6Lc7AgijY4voVyis,4468 +pip/_internal/vcs/__init__.py,sha256=UAqvzpbi0VbZo3Ub6skEeZAw-ooIZR-zX_WpCbxyCoU,596 +pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc,, +pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc,, +pip/_internal/vcs/__pycache__/git.cpython-312.pyc,, +pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc,, +pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc,, +pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc,, +pip/_internal/vcs/bazaar.py,sha256=3W1eHjkYx2vc6boeb2NBh4I_rlGAXM-vrzfNhLm1Rxg,3734 +pip/_internal/vcs/git.py,sha256=L52rqJN--Kq0kjAR8_9Si8RJEcNlULJdk6Vipg8ZlfA,19273 +pip/_internal/vcs/mercurial.py,sha256=w1ZJWLKqNP1onEjkfjlwBVnMqPZNSIER8ayjQcnTq4w,5575 +pip/_internal/vcs/subversion.py,sha256=uUgdPvxmvEB8Qwtjr0Hc0XgFjbiNi5cbvI4vARLOJXo,11787 +pip/_internal/vcs/versioncontrol.py,sha256=JsUgTG53DkJPy5mXVBH7uqeFQyaL69707ibts5uCBYA,22579 +pip/_internal/wheel_builder.py,sha256=kdPvAcxfknA8B_4g6hPDjB7C4uzL_cV5Vde2Oj2Ipzk,9146 +pip/_vendor/README.rst,sha256=t7IinjaiuwUh812XmVApQHJb8pTw33U8A9URqy6GlF4,9222 +pip/_vendor/__init__.py,sha256=WzusPTGWIMeQQWSVJ0h2rafGkVTa9WKJ2HT-2-EoZrU,4907 +pip/_vendor/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/bom.cdx.json,sha256=e-l2ABunD-azhTCvEwY76pbhvYlMJxxOV5CMc3B575M,5325 +pip/_vendor/cachecontrol/LICENSE.txt,sha256=hu7uh74qQ_P_H1ZJb0UfaSQ5JvAl_tuwM2ZsMExMFhs,558 +pip/_vendor/cachecontrol/__init__.py,sha256=GxwRkm_TQBtPZpfpVK9r6S9dAy2DVnVgDVHJKTiPZ1k,820 +pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc,, +pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc,, +pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc,, +pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc,, +pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc,, +pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc,, +pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc,, +pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc,, +pip/_vendor/cachecontrol/_cmd.py,sha256=iist2EpzJvDVIhMAxXq8iFnTBsiZAd6iplxfmNboNyk,1737 +pip/_vendor/cachecontrol/adapter.py,sha256=W-HW-l01gyCsnxkOyCbqx7sxrWYoBbKrDsKkVVQN6NE,6586 +pip/_vendor/cachecontrol/cache.py,sha256=OXwv7Fn2AwnKNiahJHnjtvaKLndvVLv_-zO-ltlV9qI,1953 +pip/_vendor/cachecontrol/caches/__init__.py,sha256=dtrrroK5BnADR1GWjCZ19aZ0tFsMfvFBtLQQU1sp_ag,303 +pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc,, +pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc,, +pip/_vendor/cachecontrol/caches/file_cache.py,sha256=d8upFmy_zwaCmlbWEVBlLXFddt8Zw8c5SFpxeOZsdfw,4117 +pip/_vendor/cachecontrol/caches/redis_cache.py,sha256=9rmqwtYu_ljVkW6_oLqbC7EaX_a8YT_yLuna-eS0dgo,1386 +pip/_vendor/cachecontrol/controller.py,sha256=xBauC-vUSu5GsJsxD4-W-JaKqqbBz0MN6Zv8PA2N8hI,19102 +pip/_vendor/cachecontrol/filewrapper.py,sha256=DhxC_rSk-beKdbsYhfvBUDovQHX9r3gHH_jP9-q_mKk,4354 +pip/_vendor/cachecontrol/heuristics.py,sha256=gqMXU8w0gQuEQiSdu3Yg-0vd9kW7nrWKbLca75rheGE,4881 +pip/_vendor/cachecontrol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/cachecontrol/serialize.py,sha256=HQd2IllQ05HzPkVLMXTF2uX5mjEQjDBkxCqUJUODpZk,5163 +pip/_vendor/cachecontrol/wrapper.py,sha256=hsGc7g8QGQTT-4f8tgz3AM5qwScg6FO0BSdLSRdEvpU,1417 +pip/_vendor/certifi/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989 +pip/_vendor/certifi/__init__.py,sha256=-Mz8ECNZmMft00Dy8wW189ca7ZeK6oKgKvMNeHe-6QI,94 +pip/_vendor/certifi/__main__.py,sha256=1k3Cr95vCxxGRGDljrW3wMdpZdL3Nhf0u1n-k2qdsCY,255 +pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc,, +pip/_vendor/certifi/__pycache__/core.cpython-312.pyc,, +pip/_vendor/certifi/cacert.pem,sha256=UPefmNrBkrINv3PzXFbwsMVXqyZ_9RmdERfMEYC3B4U,238856 +pip/_vendor/certifi/core.py,sha256=gu_ECVI1m3Rq0ytpsNE61hgQGcKaOAt9Rs9G8KsTCOI,3442 +pip/_vendor/certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/certifi/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/certifi/tests/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/certifi/tests/__pycache__/test_certify.cpython-312.pyc,, +pip/_vendor/certifi/tests/test_certify.py,sha256=TGSVk2vx_aS0er2_3HYheaN4V-1nZWTVwgzMJJKVjF8,484 +pip/_vendor/distlib/LICENSE.txt,sha256=gI4QyKarjesUn_mz-xn0R6gICUYG1xKpylf-rTVSWZ0,14531 +pip/_vendor/distlib/__init__.py,sha256=tmYThB1xmgfRjRtyy8YP0i0ymrF2n6eVswcwK6OYbcc,625 +pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc,, +pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc,, +pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc,, +pip/_vendor/distlib/__pycache__/util.cpython-312.pyc,, +pip/_vendor/distlib/compat.py,sha256=HTzhOY98c_fyMVE7xtVuZZ-AVkN4-RGes-hWmvuwquk,40605 +pip/_vendor/distlib/resources.py,sha256=wZt_Ako8TY-L6ZIAmP42J7MNX0XTEF6gkM0Om5F7UPY,11350 +pip/_vendor/distlib/scripts.py,sha256=rVm4A-YfGQDpsORwOy7AwepyRTayuy6rc-MnaCMBAcM,18835 +pip/_vendor/distlib/t32.exe,sha256=a0GV5kCoWsMutvliiCKmIgV98eRZ33wXoS-XrqvJQVs,97792 +pip/_vendor/distlib/t64-arm.exe,sha256=68TAa32V504xVBnufojh0PcenpR3U4wAqTqf-MZqbPw,182784 +pip/_vendor/distlib/t64.exe,sha256=gaYY8hy4fbkHYTTnA4i26ct8IQZzkBG2pRdy0iyuBrc,108032 +pip/_vendor/distlib/util.py,sha256=n2QDXvB0XtwrtehygnwdLgLkLia58sZjzgZPKyD6rAE,68167 +pip/_vendor/distlib/w32.exe,sha256=R4csx3-OGM9kL4aPIzQKRo5TfmRSHZo6QWyLhDhNBks,91648 +pip/_vendor/distlib/w64-arm.exe,sha256=xdyYhKj0WDcVUOCb05blQYvzdYIKMbmJn2SZvzkcey4,168448 +pip/_vendor/distlib/w64.exe,sha256=ejGf-rojoBfXseGLpya6bFTFPWRG21X5KvU8J5iU-K0,101888 +pip/_vendor/distro/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325 +pip/_vendor/distro/__init__.py,sha256=2fHjF-SfgPvjyNZ1iHh_wjqWdR_Yo5ODHwZC0jLBPhc,981 +pip/_vendor/distro/__main__.py,sha256=bu9d3TifoKciZFcqRBuygV3GSuThnVD_m2IK4cz96Vs,64 +pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc,, +pip/_vendor/distro/__pycache__/distro.cpython-312.pyc,, +pip/_vendor/distro/distro.py,sha256=XqbefacAhDT4zr_trnbA15eY8vdK4GTghgmvUGrEM_4,49430 +pip/_vendor/distro/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/idna/LICENSE.md,sha256=GppPDj1HmickDd1ZqRN6ZqtKD539yMphiMwL_YUYfwQ,1541 +pip/_vendor/idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868 +pip/_vendor/idna/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83 +pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/__main__.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/cli.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/codec.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/compat.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/core.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc,, +pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc,, +pip/_vendor/idna/cli.py,sha256=swqJLMNc8Uzs60KziNpbWnHuqlG3WRQwJSbo4n8xDAo,4139 +pip/_vendor/idna/codec.py,sha256=JRbo-f7pEkLdWeiH89Z72UR4VBYhvKDFrQBeNX6sRDE,5040 +pip/_vendor/idna/compat.py,sha256=AepA39ceRHxkfHP41-FvKW5Ki-f4PfUZ90RUMlCNdmo,1353 +pip/_vendor/idna/core.py,sha256=SfOr1xO3PoE0RDYx7bMciAnjiyjJPbPw_93AB5IUYOw,24685 +pip/_vendor/idna/idnadata.py,sha256=Af-mo8WBmkhAK6TyXKOQH88OX0mQNDKtdL7UWtQpppk,44862 +pip/_vendor/idna/intranges.py,sha256=g49scLSkqJtAhLmOODa7hVHriSjmb60tiTsEoocJdBI,1851 +pip/_vendor/idna/package_data.py,sha256=TeI94EqAFAFaXfBJwsOPUMLn2969uirPa-DaeaceAyU,21 +pip/_vendor/idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/idna/uts46data.py,sha256=jujNz5QqWMcJf-XYLv4X1jBvb5FlI0t6-e1mILsgbPk,234325 +pip/_vendor/msgpack/COPYING,sha256=SS3tuoXaWHL3jmCRvNH-pHTWYNNay03ulkuKqz8AdCc,614 +pip/_vendor/msgpack/__init__.py,sha256=RA8gcqK17YpkxBnNwXJVa1oa2LygWDgfF1nA1NPw3mo,1109 +pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc,, +pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc,, +pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc,, +pip/_vendor/msgpack/exceptions.py,sha256=dCTWei8dpkrMsQDcjQk74ATl9HsIBH0ybt8zOPNqMYc,1081 +pip/_vendor/msgpack/ext.py,sha256=kteJv03n9tYzd5oo3xYopVTo4vRaAxonBQQJhXohZZo,5726 +pip/_vendor/msgpack/fallback.py,sha256=0g1Pzp0vtmBEmJ5w9F3s_-JMVURP8RS4G1cc5TRaAsI,32390 +pip/_vendor/packaging/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197 +pip/_vendor/packaging/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174 +pip/_vendor/packaging/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344 +pip/_vendor/packaging/__init__.py,sha256=QhMEdPu2XogrJzV3S0KWS6t7l0I9k8EeDRJl4fnw87s,494 +pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/dependency_groups.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/direct_url.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/errors.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/pylock.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc,, +pip/_vendor/packaging/__pycache__/version.cpython-312.pyc,, +pip/_vendor/packaging/_elffile.py,sha256=-sKkptYqzYw2-x3QByJa5mB4rfPWu1pxkZHRx1WAFCY,3211 +pip/_vendor/packaging/_manylinux.py,sha256=Hf6nB0cOrayEs96-p3oIXAgGnFquv20DO5l-o2_Xnv0,9559 +pip/_vendor/packaging/_musllinux.py,sha256=Z6swjH3MA7XS3qXnmMN7QPhqP3fnoYI0eQ18e9-HgAE,2707 +pip/_vendor/packaging/_parser.py,sha256=Kf2nsDw4c54X82pY8ba4F02Bve6OygGMAjL-Begqcew,11698 +pip/_vendor/packaging/_structures.py,sha256=60jRbF78p8z5MKnNd6cAprgOadCJHV0DlmUmRBqFZcs,1109 +pip/_vendor/packaging/_tokenizer.py,sha256=tFU2Wr-ZZJdAbkXLEJo7qUQDJaIkfft9DqaifiEND7A,5391 +pip/_vendor/packaging/dependency_groups.py,sha256=XZIAVFK9uHG4RCGprmJn3VInUWMesxha_kytJuMO9eY,10218 +pip/_vendor/packaging/direct_url.py,sha256=eKmbDiPP1sLV4Mj_kCSZqqknrIyVO9Sr7JpF8KCjp4U,10917 +pip/_vendor/packaging/errors.py,sha256=6hfEYXAf8v_IF65-lFadJOMIieBP2xIKtyEXjG1nGIs,2680 +pip/_vendor/packaging/licenses/__init__.py,sha256=_Jx0XRiD_58palsWnyLrLuh59ZpGCPIPXLKdZo9OJvQ,7293 +pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-312.pyc,, +pip/_vendor/packaging/licenses/_spdx.py,sha256=WW7DXiyg68up_YND_wpRYlr1SHhiV4FfJLQffghhMxQ,51122 +pip/_vendor/packaging/markers.py,sha256=QixBVcb9D2HjwEYiuhpNkbqk9znPRbU8zNX0kR1RIrU,17067 +pip/_vendor/packaging/metadata.py,sha256=crAh0E3GVGVqPlu6EdRFsaG-Y6UYznTUqjuGKRGPv6c,38770 +pip/_vendor/packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/packaging/pylock.py,sha256=G_1gncTmDbRLY1jo4VDI9Uw-b5IErh_Q9V_BbVJTmD8,33890 +pip/_vendor/packaging/requirements.py,sha256=Q-BdEHVW5K785GBXt7RcP4UEsdIKWoWFqlrHjR4WV50,4395 +pip/_vendor/packaging/specifiers.py,sha256=3XBcSslm-YQEEEO_zrv4F6dR5wRxs4ErJvJ0vjNBfGM,71550 +pip/_vendor/packaging/tags.py,sha256=ANYHZxYQVp9BlOQYOHU5ArDNVIpTFl4KlbocYVBWnFs,34236 +pip/_vendor/packaging/utils.py,sha256=M7-JMKic2sP1YtV_8aW7eVGB-x3ADuKCiSrsVeCd2Uo,9848 +pip/_vendor/packaging/version.py,sha256=Mcu7Tf6Y1i0gQ4FXv4t0g8cbL5joAEshjgIpz_2vISI,38393 +pip/_vendor/pkg_resources/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023 +pip/_vendor/pkg_resources/__init__.py,sha256=vbTJ0_ruUgGxQjlEqsruFmiNPVyh2t9q-zyTDT053xI,124451 +pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/platformdirs/LICENSE,sha256=KeD9YukphQ6G6yjD_czwzv30-pSHkBHP-z0NS-1tTbY,1089 +pip/_vendor/platformdirs/__init__.py,sha256=gBmL_J_KdPgKiwM3dHCo-y-8CbS06QCa1XIhJ9bIgew,32389 +pip/_vendor/platformdirs/__main__.py,sha256=DQ1o1E1l0Ghuitj4uQJKAPwkcKbGzkzO0sbhzd9RbUc,1773 +pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc,, +pip/_vendor/platformdirs/__pycache__/_xdg.cpython-312.pyc,, +pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc,, +pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc,, +pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc,, +pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc,, +pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc,, +pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc,, +pip/_vendor/platformdirs/_xdg.py,sha256=5w6S07vVabEROhaUB_X7i0hmLNbdYtC9aEvPbsEIS8M,7894 +pip/_vendor/platformdirs/android.py,sha256=BBj3wfw-dfuVxsCsKsvFByKVspRDgfv8gwIzhpE1JRQ,11566 +pip/_vendor/platformdirs/api.py,sha256=EaIsFgXGu8cDTb41WM3hse_SrIoOUht8_TCo5XdOOSM,14887 +pip/_vendor/platformdirs/macos.py,sha256=PWudeDZAY9UuwLP_5rN3exSRFtYdY542hDhjah4dSWA,9538 +pip/_vendor/platformdirs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/platformdirs/unix.py,sha256=X7Cip1xOHFOnSVveqKWzU1P7jn4zuDGpRgmVxJnk4vk,13317 +pip/_vendor/platformdirs/version.py,sha256=dJvdm7L_7wobEcxrNoJtH7CtmYcG_VR5yNmtBshJ-xs,522 +pip/_vendor/platformdirs/windows.py,sha256=YOdSGNhdpxm_ydZq7uG74i9mX2_WWbcMSA4fobXwL0M,15798 +pip/_vendor/pygments/LICENSE,sha256=qdZvHVJt8C4p3Oc0NtNOVuhjL0bCdbvf_HBWnogvnxc,1331 +pip/_vendor/pygments/__init__.py,sha256=Xnd_7fJUqU_6D0bxXW0qS9M_OE9G6VW8YNPbGLWoKwQ,2986 +pip/_vendor/pygments/__main__.py,sha256=IqAQ_836Kv2CTx8r6Ie6duRQuYSPl85sbYomEwIsY14,356 +pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/console.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/style.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/token.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc,, +pip/_vendor/pygments/__pycache__/util.cpython-312.pyc,, +pip/_vendor/pygments/console.py,sha256=C189JAwhC1Qh0AKgshzb1wVDzFqBnv1VZ45kTEDIO30,1721 +pip/_vendor/pygments/filter.py,sha256=1dnbkq2AdC3AkHt3DaXwnOkTBLChl1kR6naSLwnr_tc,1913 +pip/_vendor/pygments/filters/__init__.py,sha256=Prd6xUK_7kw0rQv_OXPY3871RzwtWKGp_g7SddYHBXg,40397 +pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/pygments/formatter.py,sha256=EuAXPX6c3XQkCt3lD0VhSPSZByG7YQk8HrCwxlf-CZQ,4393 +pip/_vendor/pygments/formatters/__init__.py,sha256=EYLDCIgBHL_DkuhNRQZlHpS2_UE5-MfsGL1hMcOa95w,5388 +pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc,, +pip/_vendor/pygments/formatters/_mapping.py,sha256=1Cw37FuQlNacnxRKmtlPX4nyLoX9_ttko5ZwscNUZZ4,4176 +pip/_vendor/pygments/lexer.py,sha256=wSXDzt2fNZJSrlfdIfnKVlheRpFkszkPmgwBJevbBNo,35394 +pip/_vendor/pygments/lexers/__init__.py,sha256=s8I_tVlotrM8cFTGlnjw45UY5sj5jhDwdQzOOjhzgNg,12118 +pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc,, +pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc,, +pip/_vendor/pygments/lexers/_mapping.py,sha256=wGAYjOBprDVZA01eocwcLJ6L8biM9rP2jxwr1bQJ96M,77934 +pip/_vendor/pygments/lexers/python.py,sha256=TqyfQOPhCYPx35apQL65CAj9k-4BxuzLq8MDfdm2jvE,54250 +pip/_vendor/pygments/modeline.py,sha256=me8g5rySidvPtMBOrDy2O1sMqZd02lBHMY5KDZefUws,1008 +pip/_vendor/pygments/plugin.py,sha256=P6zIw-vkSQ0k1WKHrzbBTjpwlCZPbR-6Qe_dO9Bjdu0,1928 +pip/_vendor/pygments/regexopt.py,sha256=d2hTvazlow5zzZIOCVnfeEG2CY0GrY_igH1kCSSf7ow,3308 +pip/_vendor/pygments/scanner.py,sha256=DtoLi1pOKpNu-6jiakJpSQLUi_ep29SQQhv7oAwYWME,3095 +pip/_vendor/pygments/sphinxext.py,sha256=RflpnfXB4rZLYyZ-OQ_igntVOlB8jG42xFYgEVIyHf8,7984 +pip/_vendor/pygments/style.py,sha256=wJSXOXjehGYxI6e3DGaUtONeWuhmTfswfkysrL9Was0,6423 +pip/_vendor/pygments/styles/__init__.py,sha256=kYdCGK8gZ1V8wfFQETOzpPmZTJ1nMB0b1iNlU8dHQ9E,2045 +pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc,, +pip/_vendor/pygments/styles/_mapping.py,sha256=6lovFUE29tz6EsV3XYY4hgozJ7q1JL7cfO3UOlgnS8w,3312 +pip/_vendor/pygments/token.py,sha256=DVil5T2ltHkTgQTUYy1dMtgyigjavCs1ypE4Qf2CrGo,6229 +pip/_vendor/pygments/unistring.py,sha256=Z4w4HfOVUhueCURRkfhAqQ2b-69UzkhY4xuHevYEy5g,63211 +pip/_vendor/pygments/util.py,sha256=zk935tJSpwSA9zxNmV1TuhcEP0MfboXiRZSGQnLPEqk,10046 +pip/_vendor/pyproject_hooks/LICENSE,sha256=GyKwSbUmfW38I6Z79KhNjsBLn9-xpR02DkK0NCyLQVQ,1081 +pip/_vendor/pyproject_hooks/__init__.py,sha256=cPB_a9LXz5xvsRbX1o2qyAdjLatZJdQ_Lc5McNX-X7Y,691 +pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc,, +pip/_vendor/pyproject_hooks/_impl.py,sha256=jY-raxnmyRyB57ruAitrJRUzEexuAhGTpgMygqx67Z4,14936 +pip/_vendor/pyproject_hooks/_in_process/__init__.py,sha256=MJNPpfIxcO-FghxpBbxkG1rFiQf6HOUbV4U5mq0HFns,557 +pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc,, +pip/_vendor/pyproject_hooks/_in_process/_in_process.py,sha256=qcXMhmx__MIJq10gGHW3mA4Tl8dy8YzHMccwnNoKlw0,12216 +pip/_vendor/pyproject_hooks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/requests/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142 +pip/_vendor/requests/__init__.py,sha256=nC2attFwdoYN-oE6-vQW4ciuvIu7J2P_TU1qQ2DcueE,5873 +pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/_types.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/api.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/auth.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/certs.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/compat.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/help.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/models.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/packages.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/structures.cpython-312.pyc,, +pip/_vendor/requests/__pycache__/utils.cpython-312.pyc,, +pip/_vendor/requests/__version__.py,sha256=q7qQZwsDcKPcQNl8LepGo11L7JQf8Q18PPMJjvBKF_k,435 +pip/_vendor/requests/_internal_utils.py,sha256=TH2NEyyYmPx9cV5HPzrHR4XdxKuW0skkD4eDXcbZgf8,1542 +pip/_vendor/requests/_types.py,sha256=0G33n1SiecDMP67FlsRGJG1a0U7f1SnrMo3vvWKkSok,5838 +pip/_vendor/requests/adapters.py,sha256=5kSKhXlJuR_enrOjd3khSGpRBj1WqCh08Qssu0CEZ8M,28208 +pip/_vendor/requests/api.py,sha256=TRVICsBG8Ikgl5joZQR270oo6-b4G0AHWPjvQuxrVQk,7152 +pip/_vendor/requests/auth.py,sha256=6TlRpVLUw8X9m4Sl4dSbA8qrpHI8T_ES2mBTYA7IWmU,12233 +pip/_vendor/requests/certs.py,sha256=eD1G0RoMZ3kA0lydkw7oo0lmcKqvD1hhz6yOlgSKm8w,442 +pip/_vendor/requests/compat.py,sha256=1UheFGomceWe7aysgboYWxcEOH_HpQFmKhIyPiAanog,2035 +pip/_vendor/requests/cookies.py,sha256=ChzmFA8rlCBSLFCA41bmifp5bCUi9VyhCCb17Y4Kcjw,21549 +pip/_vendor/requests/exceptions.py,sha256=Am6y4ncoBptpmesOjRxc5iiuBDYjx3UN1oRVBlNgRyE,4576 +pip/_vendor/requests/help.py,sha256=TVWejkCROwKexHHB44KCZSr-c0o5XakNL7LT1UJlRy8,4114 +pip/_vendor/requests/hooks.py,sha256=69igJHXTGg5HOo9VPpUB_0NkW5VjiFrVKETnpj8Ndqs,1138 +pip/_vendor/requests/models.py,sha256=99d7WF4pR35NHo9jrbGiBocZ3LCMsloVU0DAcQ49x3Q,41848 +pip/_vendor/requests/packages.py,sha256=_ZQDCJTJ8SP3kVWunSqBsRZNPzj2c1WFVqbdr08pz3U,1057 +pip/_vendor/requests/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/requests/sessions.py,sha256=lvuzC7vwalmlJo0TtXiFFJdWqj8xaVtcFeQd17svZ6Y,34248 +pip/_vendor/requests/status_codes.py,sha256=GVD0fInPGAGXh-B9jOSPZtazjmIvfZTXCpAkf-5sBA4,4351 +pip/_vendor/requests/structures.py,sha256=upRgw5B48l5vHSokrJQaxvjS7pcZf6jI0MJi2KHmegI,4134 +pip/_vendor/requests/utils.py,sha256=u7MICyzQTvfUa3asg9nBvcrnhSIPEc6qhJSxiGGwTOk,36334 +pip/_vendor/resolvelib/LICENSE,sha256=84j9OMrRMRLB3A9mm76A5_hFQe26-3LzAw0sp2QsPJ0,751 +pip/_vendor/resolvelib/__init__.py,sha256=yoX-d4STvwGGCiQRE5cJC9Cter69SgVgqClxOCvSP7M,541 +pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc,, +pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc,, +pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc,, +pip/_vendor/resolvelib/providers.py,sha256=pIWJbIdJJ9GFtNbtwTH0Ia43Vj6hYCEJj2DOLue15FM,8914 +pip/_vendor/resolvelib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/resolvelib/reporters.py,sha256=pNJf4nFxLpAeKxlBUi2GEj0a2Ij1nikY0UabTKXesT4,2037 +pip/_vendor/resolvelib/resolvers/__init__.py,sha256=728M3EvmnPbVXS7ExXlv2kMu6b7wEsoPutEfl-uVk_I,640 +pip/_vendor/resolvelib/resolvers/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/resolvelib/resolvers/__pycache__/abstract.cpython-312.pyc,, +pip/_vendor/resolvelib/resolvers/__pycache__/criterion.cpython-312.pyc,, +pip/_vendor/resolvelib/resolvers/__pycache__/exceptions.cpython-312.pyc,, +pip/_vendor/resolvelib/resolvers/__pycache__/resolution.cpython-312.pyc,, +pip/_vendor/resolvelib/resolvers/abstract.py,sha256=CNeQPnpAudY77nmzOkONSmAgRlzIf06X-X9mvRYODms,1543 +pip/_vendor/resolvelib/resolvers/criterion.py,sha256=lcmZGv5sKHOnFD_RzZwvlGSj19MeA-5rCMpdf2Sgw7Y,1768 +pip/_vendor/resolvelib/resolvers/exceptions.py,sha256=ln_jaQtgLlRUSFY627yiHG2gD7AgaXzRKaElFVh7fDQ,1768 +pip/_vendor/resolvelib/resolvers/resolution.py,sha256=3J_zkW-sD3EY-BlNXjyln__njpyH5n0UZJT6uV7CheA,24212 +pip/_vendor/resolvelib/structs.py,sha256=pu-EJiR2IBITr2SQeNPRa0rXhjlStfmO_GEgAhr3004,6420 +pip/_vendor/rich/LICENSE,sha256=3u18F6QxgVgZCj6iOcyHmlpQJxzruYrnAl9I--WNyhU,1056 +pip/_vendor/rich/__init__.py,sha256=dRxjIL-SbFVY0q3IjSMrfgBTHrm1LZDgLOygVBwiYZc,6090 +pip/_vendor/rich/__main__.py,sha256=e_aVC-tDzarWQW9SuZMuCgBr6ODV_iDNV2Wh2xkxOlw,7896 +pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/abc.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/align.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/bar.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/box.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/cells.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/color.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/columns.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/console.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/containers.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/control.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/errors.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/json.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/layout.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/live.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/logging.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/markup.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/measure.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/padding.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/pager.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/palette.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/panel.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/progress.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/region.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/repr.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/rule.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/scope.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/screen.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/segment.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/status.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/style.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/styled.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/table.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/text.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/theme.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/themes.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc,, +pip/_vendor/rich/__pycache__/tree.cpython-312.pyc,, +pip/_vendor/rich/_cell_widths.py,sha256=fbmeyetEdHjzE_Vx2l1uK7tnPOhMs2X1lJfO3vsKDpA,10209 +pip/_vendor/rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235 +pip/_vendor/rich/_emoji_replace.py,sha256=n-kcetsEUx2ZUmhQrfeMNc-teeGhpuSQ5F8VPBsyvDo,1064 +pip/_vendor/rich/_export_format.py,sha256=RI08pSrm5tBSzPMvnbTqbD9WIalaOoN5d4M1RTmLq1Y,2128 +pip/_vendor/rich/_extension.py,sha256=Xt47QacCKwYruzjDi-gOBq724JReDj9Cm9xUi5fr-34,265 +pip/_vendor/rich/_fileno.py,sha256=HWZxP5C2ajMbHryvAQZseflVfQoGzsKOHzKGsLD8ynQ,799 +pip/_vendor/rich/_inspect.py,sha256=ROT0PLC2GMWialWZkqJIjmYq7INRijQQkoSokWTaAiI,9656 +pip/_vendor/rich/_log_render.py,sha256=1ByI0PA1ZpxZY3CGJOK54hjlq4X-Bz_boIjIqCd8Kns,3225 +pip/_vendor/rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236 +pip/_vendor/rich/_null_file.py,sha256=ADGKp1yt-k70FMKV6tnqCqecB-rSJzp-WQsD7LPL-kg,1394 +pip/_vendor/rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063 +pip/_vendor/rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423 +pip/_vendor/rich/_ratio.py,sha256=IOtl78sQCYZsmHyxhe45krkb68u9xVz7zFsXVJD-b2Y,5325 +pip/_vendor/rich/_spinners.py,sha256=U2r1_g_1zSjsjiUdAESc2iAMc3i4ri_S8PYP6kQ5z1I,19919 +pip/_vendor/rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351 +pip/_vendor/rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417 +pip/_vendor/rich/_win32_console.py,sha256=BSaDRIMwBLITn_m0mTRLPqME5q-quGdSMuYMpYeYJwc,22755 +pip/_vendor/rich/_windows.py,sha256=aBwaD_S56SbgopIvayVmpk0Y28uwY2C5Bab1wl3Bp-I,1925 +pip/_vendor/rich/_windows_renderer.py,sha256=t74ZL3xuDCP3nmTp9pH1L5LiI2cakJuQRQleHCJerlk,2783 +pip/_vendor/rich/_wrap.py,sha256=FlSsom5EX0LVkA3KWy34yHnCfLtqX-ZIepXKh-70rpc,3404 +pip/_vendor/rich/abc.py,sha256=ON-E-ZqSSheZ88VrKX2M3PXpFbGEUUZPMa_Af0l-4f0,890 +pip/_vendor/rich/align.py,sha256=dg-7uY0ukMLLlUEsBDRLva22_sQgIJD4BK0dmZHFHug,10324 +pip/_vendor/rich/ansi.py,sha256=Avs1LHbSdcyOvDOdpELZUoULcBiYewY76eNBp6uFBhs,6921 +pip/_vendor/rich/bar.py,sha256=ldbVHOzKJOnflVNuv1xS7g6dLX2E3wMnXkdPbpzJTcs,3263 +pip/_vendor/rich/box.py,sha256=kmavBc_dn73L_g_8vxWSwYJD2uzBXOUFTtJOfpbczcM,10686 +pip/_vendor/rich/cells.py,sha256=KrQkj5-LghCCpJLSNQIyAZjndc4bnEqOEmi5YuZ9UCY,5130 +pip/_vendor/rich/color.py,sha256=3HSULVDj7qQkXUdFWv78JOiSZzfy5y1nkcYhna296V0,18211 +pip/_vendor/rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054 +pip/_vendor/rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131 +pip/_vendor/rich/console.py,sha256=t9azZpmRMVU5cphVBZSShNsmBxd2-IAWcTTlhor-E1s,100849 +pip/_vendor/rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288 +pip/_vendor/rich/containers.py,sha256=c_56TxcedGYqDepHBMTuZdUIijitAQgnox-Qde0Z1qo,5502 +pip/_vendor/rich/control.py,sha256=EUTSUFLQbxY6Zmo_sdM-5Ls323vIHTBfN8TPulqeHUY,6487 +pip/_vendor/rich/default_styles.py,sha256=khQFqqaoDs3bprMqWpHw8nO5UpG2DN6QtuTd6LzZwYc,8257 +pip/_vendor/rich/diagnose.py,sha256=fJl1TItRn19gGwouqTg-8zPUW3YqQBqGltrfPQs1H9w,1025 +pip/_vendor/rich/emoji.py,sha256=Wd4bQubZdSy6-PyrRQNuMHtn2VkljK9uPZPVlu2cmx0,2367 +pip/_vendor/rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642 +pip/_vendor/rich/file_proxy.py,sha256=Tl9THMDZ-Pk5Wm8sI1gGg_U5DhusmxD-FZ0fUbcU0W0,1683 +pip/_vendor/rich/filesize.py,sha256=_iz9lIpRgvW7MNSeCZnLg-HwzbP4GETg543WqD8SFs0,2484 +pip/_vendor/rich/highlighter.py,sha256=G_sn-8DKjM1sEjLG_oc4ovkWmiUpWvj8bXi0yed2LnY,9586 +pip/_vendor/rich/json.py,sha256=vVEoKdawoJRjAFayPwXkMBPLy7RSTs-f44wSQDR2nJ0,5031 +pip/_vendor/rich/jupyter.py,sha256=QyoKoE_8IdCbrtiSHp9TsTSNyTHY0FO5whE7jOTd9UE,3252 +pip/_vendor/rich/layout.py,sha256=ajkSFAtEVv9EFTcFs-w4uZfft7nEXhNzL7ZVdgrT5rI,14004 +pip/_vendor/rich/live.py,sha256=tF3ukAAJZ_N2ZbGclqZ-iwLoIoZ8f0HHUz79jAyJqj8,15180 +pip/_vendor/rich/live_render.py,sha256=It_39YdzrBm8o3LL0kaGorPFg-BfZWAcrBjLjFokbx4,3521 +pip/_vendor/rich/logging.py,sha256=5KaPPSMP9FxcXPBcKM4cGd_zW78PMgf-YbMVnvfSw0o,12468 +pip/_vendor/rich/markup.py,sha256=3euGKP5s41NCQwaSjTnJxus5iZMHjxpIM0W6fCxra38,8451 +pip/_vendor/rich/measure.py,sha256=HmrIJX8sWRTHbgh8MxEay_83VkqNW_70s8aKP5ZcYI8,5305 +pip/_vendor/rich/padding.py,sha256=KVEI3tOwo9sgK1YNSuH__M1_jUWmLZwRVV_KmOtVzyM,4908 +pip/_vendor/rich/pager.py,sha256=SO_ETBFKbg3n_AgOzXm41Sv36YxXAyI3_R-KOY2_uSc,828 +pip/_vendor/rich/palette.py,sha256=lInvR1ODDT2f3UZMfL1grq7dY_pDdKHw4bdUgOGaM4Y,3396 +pip/_vendor/rich/panel.py,sha256=9sQl00hPIqH5G2gALQo4NepFwpP0k9wT-s_gOms5pIc,11157 +pip/_vendor/rich/pretty.py,sha256=gy3S72u4FRg2ytoo7N1ZDWDIvB4unbzd5iUGdgm-8fc,36391 +pip/_vendor/rich/progress.py,sha256=CUc2lkU-X59mVdGfjMCBkZeiGPL3uxdONjhNJF2T7wY,60408 +pip/_vendor/rich/progress_bar.py,sha256=mZTPpJUwcfcdgQCTTz3kyY-fc79ddLwtx6Ghhxfo064,8162 +pip/_vendor/rich/prompt.py,sha256=l0RhQU-0UVTV9e08xW1BbIj0Jq2IXyChX4lC0lFNzt4,12447 +pip/_vendor/rich/protocol.py,sha256=5hHHDDNHckdk8iWH5zEbi-zuIVSF5hbU2jIo47R7lTE,1391 +pip/_vendor/rich/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166 +pip/_vendor/rich/repr.py,sha256=5MZJZmONgC6kud-QW-_m1okXwL2aR6u6y-pUcUCJz28,4431 +pip/_vendor/rich/rule.py,sha256=0fNaS_aERa3UMRc3T5WMpN_sumtDxfaor2y3of1ftBk,4602 +pip/_vendor/rich/scope.py,sha256=TMUU8qo17thyqQCPqjDLYpg_UU1k5qVd-WwiJvnJVas,2843 +pip/_vendor/rich/screen.py,sha256=YoeReESUhx74grqb0mSSb9lghhysWmFHYhsbMVQjXO8,1591 +pip/_vendor/rich/segment.py,sha256=otnKeKGEV-WRlQVosfJVeFDcDxAKHpvJ_hLzSu5lumM,24743 +pip/_vendor/rich/spinner.py,sha256=onIhpKlljRHppTZasxO8kXgtYyCHUkpSgKglRJ3o51g,4214 +pip/_vendor/rich/status.py,sha256=kkPph3YeAZBo-X-4wPp8gTqZyU466NLwZBA4PZTTewo,4424 +pip/_vendor/rich/style.py,sha256=W9Ccy8Py8lNICtlfcp-ryzMTuQaGxAU3av7-g5fHu0s,26990 +pip/_vendor/rich/styled.py,sha256=eZNnzGrI4ki_54pgY3Oj0T-x3lxdXTYh4_ryDB24wBU,1258 +pip/_vendor/rich/syntax.py,sha256=eDKIRwl--eZ0Lwo2da2RRtfutXGavrJO61Cl5OkS59U,36371 +pip/_vendor/rich/table.py,sha256=ZmT7V7MMCOYKw7TGY9SZLyYDf6JdM-WVf07FdVuVhTI,40049 +pip/_vendor/rich/terminal_theme.py,sha256=1j5-ufJfnvlAo5Qsi_ACZiXDmwMXzqgmFByObT9-yJY,3370 +pip/_vendor/rich/text.py,sha256=AO7JPCz6-gaN1thVLXMBntEmDPVYFgFNG1oM61_sanU,47552 +pip/_vendor/rich/theme.py,sha256=oNyhXhGagtDlbDye3tVu3esWOWk0vNkuxFw-_unlaK0,3771 +pip/_vendor/rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102 +pip/_vendor/rich/traceback.py,sha256=c0WmB_L04_UfZbLaoH982_U_s7eosxKMUiAVmDPdRYU,35861 +pip/_vendor/rich/tree.py,sha256=yWnQ6rAvRGJ3qZGqBrxS2SW2TKBTNrP0SdY8QxOFPuw,9451 +pip/_vendor/tomli/LICENSE,sha256=uAgWsNUwuKzLTCIReDeQmEpuO2GSLCte6S8zcqsnQv4,1072 +pip/_vendor/tomli/__init__.py,sha256=nrBC18DbXRTCFo7ElG5BDeWpHJzOhokvXk215GM8Z2I,314 +pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc,, +pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc,, +pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc,, +pip/_vendor/tomli/_parser.py,sha256=pBIjTIa_cQs2HglDJ2lh8OJfptfDa6eg5-7Iej4BjHs,26440 +pip/_vendor/tomli/_re.py,sha256=oSNZ_ilFI6chEuQ01YRSoUydBQr_okF_mSdHTkFmv90,3396 +pip/_vendor/tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254 +pip/_vendor/tomli/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26 +pip/_vendor/tomli_w/LICENSE,sha256=uAgWsNUwuKzLTCIReDeQmEpuO2GSLCte6S8zcqsnQv4,1072 +pip/_vendor/tomli_w/__init__.py,sha256=0F8yDtXx3Uunhm874KrAcP76srsM98y7WyHQwCulZbo,169 +pip/_vendor/tomli_w/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/tomli_w/__pycache__/_writer.cpython-312.pyc,, +pip/_vendor/tomli_w/_writer.py,sha256=dsifFS2xYf1i76mmRyfz9y125xC7Z_HQ845ZKhJsYXs,6961 +pip/_vendor/tomli_w/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26 +pip/_vendor/truststore/LICENSE,sha256=M757fo-k_Rmxdg4ajtimaL2rhSyRtpLdQUJLy3Jan8o,1086 +pip/_vendor/truststore/__init__.py,sha256=Bu7kqkmpunhLsj5xCu8gT_25ktoPXcSnwe8VHk1GmJo,1320 +pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc,, +pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc,, +pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc,, +pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc,, +pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc,, +pip/_vendor/truststore/_api.py,sha256=CYJCV5BTfttZYfqY3movdMBE-8az7uhET_LYbKT2Nn4,11413 +pip/_vendor/truststore/_macos.py,sha256=nZlLkOmszUE0g6ryRwBVGY5COzPyudcsiJtDWarM5LQ,20503 +pip/_vendor/truststore/_openssl.py,sha256=zB-SQvJydks7tQ0yIwrP6GD3fQNSSaPiq7zw4yF5T40,2412 +pip/_vendor/truststore/_ssl_constants.py,sha256=NUD4fVKdSD02ri7-db0tnO0VqLP9aHuzmStcW7tAl08,1130 +pip/_vendor/truststore/_windows.py,sha256=rAHyKYD8M7t-bXfG8VgOVa3TpfhVhbt4rZQlO45YuP8,17993 +pip/_vendor/truststore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/LICENSE.txt,sha256=Ew46ZNX91dCWp1JpRjSn2d8oRGnehuVzIQAmgEHj1oY,1093 +pip/_vendor/urllib3/__init__.py,sha256=JMo1tg1nIV1AeJ2vENC_Txfl0e5h6Gzl9DGVk1rWRbo,6979 +pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/_base_connection.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/_request_methods.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc,, +pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc,, +pip/_vendor/urllib3/_base_connection.py,sha256=HzcSEHexgDrRUr60jNniB2KQjdw97SedD5luRfHtCXg,5580 +pip/_vendor/urllib3/_collections.py,sha256=aOVm2mKilvuvT1efAGtkA7pi65C1NC3HJfpFxvYBS8A,17522 +pip/_vendor/urllib3/_request_methods.py,sha256=gCeF85SO_UU4WoPwYHIoz_tw-eM_EVOkLFp8OFsC7DA,9931 +pip/_vendor/urllib3/_version.py,sha256=-gMrKhsPiOj0XzUezVFa59d1S6QgQiKgQT5gGgyM8-w,520 +pip/_vendor/urllib3/connection.py,sha256=Zos3qxKDW9-GQ6aVqfBQVRM5soB0IjHxY_xXWpJaFTI,42786 +pip/_vendor/urllib3/connectionpool.py,sha256=sGFnddXYwlx7KC4JCP1gKvdNGLNK-YTCJDdGACGj3Y8,44164 +pip/_vendor/urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc,, +pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc,, +pip/_vendor/urllib3/contrib/emscripten/__init__.py,sha256=ZruXaWKVzAEJdqNH3NEh0mrHrw--2VZYb0zX0RonNZA,870 +pip/_vendor/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc,, +pip/_vendor/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc,, +pip/_vendor/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc,, +pip/_vendor/urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc,, +pip/_vendor/urllib3/contrib/emscripten/connection.py,sha256=giElsBoUsKVURbZzb8GCrJmqW23Xnvj2aNyQVF42slg,8960 +pip/_vendor/urllib3/contrib/emscripten/emscripten_fetch_worker.js,sha256=z1k3zZ4_hDKd3-tN7wzz8LHjHC2pxN_uu8B3k9D9A3c,3677 +pip/_vendor/urllib3/contrib/emscripten/fetch.py,sha256=5xcd--viFxZd2nBy0aK73dtJ9Tsh1yYZU_SUXwnwibk,23520 +pip/_vendor/urllib3/contrib/emscripten/request.py,sha256=mL28szy1KvE3NJhWor5jNmarp8gwplDU-7gwGZY5g0Q,566 +pip/_vendor/urllib3/contrib/emscripten/response.py,sha256=CDpY0GFoluR3IxECkM2gH5uDfYDM0EL1FWr7B76wtgM,9719 +pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=Pi_qwvdl83tNpuj1k5pCpP_gmxlk4BfFrTlAeg-qZ_U,19760 +pip/_vendor/urllib3/contrib/socks.py,sha256=rSuklfha4-vto-8qSLhSPmf5lmRPIYuqdAxKe9bg2RA,7639 +pip/_vendor/urllib3/exceptions.py,sha256=hQPHoqo4yw46esNSVkciVQXVUgAxWAglsh6MbyQta-Q,9945 +pip/_vendor/urllib3/fields.py,sha256=aGLFAVZpVU-FbJlllve4Ahg0-pH9ZZBQ2Iz7lfpkjkM,10801 +pip/_vendor/urllib3/filepost.py,sha256=U8eNZ-mpKKHhrlbHEEiTxxgK16IejhEa7uz42yqA_dI,2388 +pip/_vendor/urllib3/http2/__init__.py,sha256=xzrASH7R5ANRkPJOot5lGnATOq3KKuyXzI42rcnwmqs,1741 +pip/_vendor/urllib3/http2/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/urllib3/http2/__pycache__/connection.cpython-312.pyc,, +pip/_vendor/urllib3/http2/__pycache__/probe.cpython-312.pyc,, +pip/_vendor/urllib3/http2/connection.py,sha256=bHMH6fNvatwXPrKqrcn74yA3pUWcqPDppnK1LcKCbP8,12578 +pip/_vendor/urllib3/http2/probe.py,sha256=nnAkqbhAakOiF75rz7W0udZ38Eeh_uD8fjV74N73FEI,3014 +pip/_vendor/urllib3/poolmanager.py,sha256=grIrETG1rgHYBaQau1yN4xxfK8U9CKVRYmwBXW_kdSo,23929 +pip/_vendor/urllib3/py.typed,sha256=UaCuPFa3H8UAakbt-5G8SPacldTOGvJv18pPjUJ5gDY,93 +pip/_vendor/urllib3/response.py,sha256=DzBM-dzl6x_h2EZGc9iw0gbOkQ36bolC7WESH4TurH8,53031 +pip/_vendor/urllib3/util/__init__.py,sha256=-qeS0QceivazvBEKDNFCAI-6ACcdDOE4TMvo7SLNlAQ,1001 +pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/util.cpython-312.pyc,, +pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc,, +pip/_vendor/urllib3/util/connection.py,sha256=JjO722lzHlzLXPTkr9ZWBdhseXnMVjMSb1DJLVrXSnQ,4444 +pip/_vendor/urllib3/util/proxy.py,sha256=seP8-Q5B6bB0dMtwPj-YcZZQ30vHuLqRu-tI0JZ2fzs,1148 +pip/_vendor/urllib3/util/request.py,sha256=p9Ki9eo1tFBO-jqV_7KmmJ60RKqoY2r4ao0SmaHLyOs,8086 +pip/_vendor/urllib3/util/response.py,sha256=vQE639uoEhj1vpjEdxu5lNIhJCSUZkd7pqllUI0BZOA,3374 +pip/_vendor/urllib3/util/retry.py,sha256=2YnSX-_FecMShD61Mx5s68J0_btUHZrrc_BkFVRS1P4,19577 +pip/_vendor/urllib3/util/ssl_.py,sha256=Oqe3rIhUU3e3GVgZob2hxmR8Q0ZDhrhESPlPP_GLaVQ,17742 +pip/_vendor/urllib3/util/ssl_match_hostname.py,sha256=Ft44KJzTzGMmKff_ZXP91li2V4WhmvEy16PKBcv4vZk,5479 +pip/_vendor/urllib3/util/ssltransport.py,sha256=Ez4O8pR_vT8dan_FvqBYS6dgDfBXEMfVfrzcdUoWfi4,8847 +pip/_vendor/urllib3/util/timeout.py,sha256=vsUJRpO0nfKk-y1OKlgFGY1ONJGPgkaZ7B7kruEpVYw,10363 +pip/_vendor/urllib3/util/url.py,sha256=PEDQMypidude0nAZctLLiFK9epN-LPnSH7KpOLLwqH0,15256 +pip/_vendor/urllib3/util/util.py,sha256=j3lbZK1jPyiwD34T8IgJzdWEZVT-4E-0vYIJi9UjeNA,1146 +pip/_vendor/urllib3/util/wait.py,sha256=_ph8IrUR3sqPqi0OopQgJUlH4wzkGeM5CiyA7XGGtmI,4423 +pip/_vendor/vendor.txt,sha256=K0S0vFt9qdzaOAmdySxSzD3aKnFM0Ym0OcBmhAFBhjA,318 +pip/py.typed,sha256=EBVvvPRTn_eIpz5e5QztSCdrMX7Qwd7VP93RSoIlZ2I,286 diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/REQUESTED b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/WHEEL b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..d8b9936dad9ab2513fa6979f411560d3b6b57e37 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/entry_points.txt b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..c6436d21fcc6479564ce8581c93e30e2cc60a096 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] +pip=pip._internal.cli.main:main +pip3=pip._internal.cli.main:main + diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/AUTHORS.txt b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/AUTHORS.txt new file mode 100644 index 0000000000000000000000000000000000000000..691991f6297ee7562b3ee9e403e602da648f1486 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/AUTHORS.txt @@ -0,0 +1,888 @@ +@Switch01 +A_Rog +Aakanksha Agrawal +Aarni Koskela +abdul rawoof +Abdul Rawoof Khan +Abhinav Sagar +ABHYUDAY PRATAP SINGH +abs51295 +AceGentile +Adam Chainz +Adam Tse +Adam Turner +Adam Wentz +admin +Adolfo Ochagavía +Adrien Morison +Agus +ahayrapetyan +Ahilya +AinsworthK +Akash Srivastava +Alan Yee +Albert Tugushev +Albert-Guan +albertg +Alberto Sottile +Aleks Bunin +Ales Erjavec +Alessandro Molina +Alethea Flowers +Alex Gaynor +Alex Grönholm +Alex Hedges +Alex Loosley +Alex Morega +Alex Stachowiak +Alexander Regueiro +Alexander Shtyrov +Alexandre Conrad +Alexey Popravka +Aleš Erjavec +Alli +Aman +Ami Fischman +Ananya Maiti +Anatoly Techtonik +Anders Kaseorg +Andre Aguiar +Andrea Corna +Andreas Lutro +Andrei Geacar +Andrew Gaul +Andrew Shymanel +Andrey Bienkowski +Andrey Bulgakov +Andrés Delfino +Andy Freeland +Andy Kluger +Ani Hayrapetyan +Aniruddha Basak +Anish Tambe +Anrs Hu +Anthony Sottile +Antoine Lambert +Antoine Musso +Anton Ovchinnikov +Anton Patrushev +Anton Zelenov +Antonio Alvarado Hernandez +Antony Lee +Antti Kaihola +Anubhav Patel +Anudit Nagar +Anuj Godase +AQNOUCH Mohammed +AraHaan +Aren Siekmeier +arena +arenasys +Arindam Choudhury +Armin Ronacher +Arnon Yaari +Artem +Arun Babu Neelicattu +Ashley Manton +Ashwin Ramaswami +atse +Atsushi Odagiri +Avinash Karhana +Avner Cohen +Awit (Ah-Wit) Ghirmai +Baptiste Mispelon +Barney Gale +barneygale +Bartek Ogryczak +Bastian Venthur +Ben Bodenmiller +Ben Darnell +Ben Hoyt +Ben Mares +Ben Rosser +Bence Nagy +Benjamin Peterson +Benjamin VanEvery +Benoit Pierre +Berker Peksag +Bernard +Bernard Tyers +Bernardo B. Marques +Bernhard M. Wiedemann +Bertil Hatt +Bhavam Vidyarthi +Blazej Michalik +Bogdan Opanchuk +BorisZZZ +Brad Erickson +Bradley Ayers +Bradley Reynolds +Branch Vincent +Brandon L. Reiss +Brandt Bucher +Brannon Dorsey +Brett Randall +Brett Rosen +Brian Cristante +Brian Rosner +briantracy +BrownTruck +Bruno Oliveira +Bruno Renié +Bruno S +Bstrdsmkr +Buck Golemon +burrows +Bussonnier Matthias +bwoodsend +c22 +Caleb Brown +Caleb Martinez +Calvin Smith +Carl Meyer +Carlos Liam +Carol Willing +Carter Thayer +Cass +Chandrasekhar Atina +Charlie Marsh +charwick +Chih-Hsuan Yen +Chris Brinker +Chris Hunt +Chris Jerdonek +Chris Kuehl +Chris Markiewicz +Chris McDonough +Chris Pawley +Chris Pryer +Chris Wolfe +Christian Clauss +Christian Heimes +Christian Oudard +Christoph Reiter +Christopher Hunt +Christopher Snyder +chrysle +cjc7373 +Clark Boylan +Claudio Jolowicz +Clay McClure +Cody +Cody Soyland +Colin Watson +Collin Anderson +Connor Osborn +Cooper Lees +Cooper Ry Lees +Cory Benfield +Cory Wright +Craig Kerstiens +Cristian Sorinel +Cristina +Cristina Muñoz +ctg123 +Curtis Doty +cytolentino +Daan De Meyer +Dale +Damian +Damian Quiroga +Damian Shaw +Dan Black +Dan Savilonis +Dan Sully +Dane Hillard +daniel +Daniel Collins +Daniel Hahler +Daniel Hollas +Daniel Holth +Daniel Jost +Daniel Katz +Daniel Shaulov +Daniele Esposti +Daniele Nicolodi +Daniele Procida +Daniil Konovalenko +Danny Hermes +Danny McClanahan +Darren Kavanagh +Dav Clark +Dave Abrahams +Dave Jones +David Aguilar +David Black +David Bordeynik +David Caro +David D Lowe +David Evans +David Hewitt +David Linke +David Poggi +David Poznik +David Pursehouse +David Runge +David Tucker +David Wales +Davidovich +Dayna Blackwell +ddelange +Deepak Sharma +Deepyaman Datta +Denis Roussel (ACSONE) +Denise Yu +dependabot[bot] +dereksz +derwolfe +Desetude +developer +Devesh Kumar +Devesh Kumar Singh +devsagul +Dewank Pant +Diego Caraballo +Diego Ramirez +DiegoCaraballo +Dimitri Merejkowsky +Dimitri Papadopoulos +Dimitri Papadopoulos Orfanos +Dirk Stolle +dkjsone +Dmitrii Sutiagin +Dmitry Gladkov +Dmitry Volodin +Domen Kožar +Dominic Davis-Foster +Donald Stufft +Dongweiming +doron zarhi +Dos Moonen +Douglas Thor +DrFeathers +Dustin Ingram +Dustin Rodrigues +Dwayne Bailey +Ed Morley +Edgar Ramírez +Edgar Ramírez Mondragón +Ee Durbin +Efflam Lemaillet +efflamlemaillet +Eitan Adler +ekristina +elainechan +Eli Schwartz +Elisha Hollander +Ellen Marie Dash +Emil Burzo +Emil Styrke +Emmanuel Arias +Endoh Takanao +enoch +Erdinc Mutlu +Eric Cousineau +Eric Gillingham +Eric Hanchrow +Eric Hopper +Erik M. Bray +Erik Rose +Erwin Janssen +Eugene Vereshchagin +everdimension +Federico +Felipe Peter +Felix Yan +fiber-space +Filip Kokosiński +Filipe Laíns +Finn Womack +finnagin +Flavio Amurrio +Florian Briand +Florian Rathgeber +Francesco +Francesco Montesano +Fredrik Orderud +Fredrik Roubert +Frost Ming +Gabriel Curio +Gabriel de Perthuis +Garry Polley +gavin +gdanielson +Gene Wood +Geoffrey Sneddon +George Margaritis +George Song +Georgi Valkov +Georgy Pchelkin +Gertjan van Zwieten +ghost +Giancarlo Cicellyn Comneno +Giftlin Rajaiah +gizmoguy1 +gkdoc +Godefroid Chapelle +Gopinath M +GOTO Hayato +gousaiyang +gpiks +Greg Roodt +Greg Ward +Guido Diepen +Guilherme Espada +Guillaume Seguin +gutsytechster +Guy Rozendorn +Guy Tuval +gzpan123 +Hanjun Kim +Hari Charan +Harsh Vardhan +Harsha Sai +harupy +Harutaka Kawamura +Hasan-8326 +hauntsaninja +Henrich Hartzer +Henry Schreiner +Herbert Pfennig +Holly Stotelmyer +Honnix +Hsiaoming Yang +Hugo +Hugo Lopes Tavares +Hugo van Kemenade +Hugues Bruant +Hynek Schlawack +iamsrp-deshaw +Ian Bicking +Ian Cordasco +Ian Lee +Ian Stapleton Cordasco +Ian Wienand +Igor Kuzmitshov +Igor Sobreira +Ikko Ashimine +Ilan Schnell +Illia Volochii +Ilya Abdolmanafi +Ilya Baryshev +Inada Naoki +Ionel Cristian Mărieș +Ionel Maries Cristian +Itamar Turner-Trauring +iTrooz +Ivan Pozdeev +J. Nick Koston +Jacob Kim +Jacob Walls +Jaime Sanz +Jake Lishman +jakirkham +Jakub Kuczys +Jakub Stasiak +Jakub Vysoky +Jakub Wilk +James +James Cleveland +James Curtin +James Firth +James Gerity +James Polley +Jan Pokorný +Jannis Leidel +Jarek Potiuk +jarondl +Jason Curtis +Jason R. Coombs +JasonMo +JasonMo1 +Jay Graves +Jean Abou Samra +Jean-Christophe Fillion-Robin +Jeff Barber +Jeff Dairiki +Jeff Widman +Jelmer Vernooij +jenix21 +Jeremy Fleischman +Jeremy Stanley +Jeremy Zafran +Jesse Rittner +Jiashuo Li +Jim Fisher +Jim Garrison +Jinzhe Zeng +Jiun Bae +Jivan Amara +Joa +Joe Bylund +Joe Michelini +Johannes Altmanninger +John Paton +John Sirois +John T. Wodder II +John-Scott Atlakson +johnthagen +Jon Banafato +Jon Dufresne +Jon Parise +Jonas Nockert +Jonathan Herbert +Joonatan Partanen +Joost Molenaar +Jorge Niedbalski +Joseph Bylund +Joseph Long +Josh Bronson +Josh Cannon +Josh Hansen +Josh Schneier +Joshua +JoshuaPerdue +Jost Migenda +Juan Luis Cano Rodríguez +Juanjo Bazán +Judah Rand +Julian Berman +Julian Gethmann +Julien Demoor +Julien Stephan +July Tikhonov +Jussi Kukkonen +Justin van Heek +jwg4 +Jyrki Pulliainen +Kai Chen +Kai Mueller +Kamal Bin Mustafa +Karolina Surma +kasium +kaustav haldar +Kaz Nishimura +keanemind +Keith Maxwell +Kelsey Hightower +Kenneth Belitzky +Kenneth Reitz +Kevin Burke +Kevin Carter +Kevin Frommelt +Kevin R Patterson +Kevin Turcios +Kexuan Sun +Kit Randel +Klaas van Schelven +KOLANICH +konstin +kpinc +Krishan Bhasin +Krishna Oza +Kumar McMillan +Kuntal Majumder +Kurt McKee +Kyle Persohn +lakshmanaram +Laszlo Kiss-Kollar +Laurent Bristiel +Laurent LAPORTE +Laurie O +Laurie Opperman +layday +Leon Sasson +Lev Givon +Lincoln de Sousa +Lipis +lorddavidiii +Loren Carvalho +Louis Liu +Lucas Cimon +Ludovic Gasc +Luis Carlos +Luis Medel +Lukas Geiger +Lukas Juhrich +Luke Macken +Luo Jiebin +luojiebin +luz.paz +László Kiss Kollár +M00nL1ght +MajorTanya +Malcolm Smith +Marc Abramowitz +Marc Tamlyn +Marcus Smith +Mariatta +Mark Kohler +Mark McLoughlin +Mark Williams +Markus Hametner +Martey Dodoo +Martin Fischer +Martin Häcker +Martin Pavlasek +Masaki +Masklinn +Matej Stuchlik +Mateusz Sokół +Mathew Jennings +Mathieu Bridon +Mathieu Kniewallner +Matt Bacchi +Matt Good +Matt Maker +Matt Robenolt +Matt Wozniski +matthew +Matthew Einhorn +Matthew Feickert +Matthew Gilliard +Matthew Hughes +Matthew Iversen +Matthew Treinish +Matthew Trumbell +Matthew Willson +Matthias Bussonnier +mattip +Maurits van Rees +Max W Chase +Maxim Kurnikov +Maxime Rouyrre +mayeut +mbaluna +Md Sujauddin Sekh +mdebi +Meet Vasita +memoselyk +meowmeowcat +Michael +Michael Aquilina +Michael E. Karpeles +Michael Klich +Michael Mintz +Michael Williamson +michaelpacer +Michał Górny +Mickaël Schoentgen +Miguel Araujo Perez +Mihir Singh +Mike +Mike Hendricks +Min RK +MinRK +Miro Hrončok +Mohsen Fooladnezhad +Monica Baluna +montefra +Monty Taylor +morotti +mrKazzila +Muha Ajjan +MUTHUSRIHEMADHARSHINI S A +Nadav Wexler +Nahuel Ambrosini +Nate Coraor +Nate Prewitt +Nathan Houghton +Nathaniel J. Smith +Nehal J Wani +Neil Botelho +Nguyễn Gia Phong +Nicholas Serra +Nick Coghlan +Nick Stenning +Nick Timkovich +Nicolas Bock +Nicole Harris +Nikhil Benesch +Nikhil Ladha +Nikita Chepanov +Nikolay Korolev +Nipunn Koorapati +Nitesh Sharma +Niyas Sait +Noah +Noah Gorny +Nok Lam Chan +Norbert Manthey +Nothing-991 +Nowell Strite +NtaleGrey +nucccc +nvdv +OBITORASU +Ofek Lev +ofrinevo +Oleg Burnaev +Oliver Freund +Oliver Jeeves +Oliver Mannion +Oliver Tonnhofer +Olivier Girardot +Olivier Grisel +Ollie Rutherfurd +Omar Abdalgwad +OMOTO Kenji +Omry Yadan +onlinejudge95 +Oren Held +Oscar Benjamin +oxygen dioxide +Oz N Tiram +Pachwenko +Paresh Joshi +Patrick Dubroy +Patrick Jenkins +Patrick Lawson +patricktokeeffe +Patrik Kopkan +Paul Ganssle +Paul Kehrer +Paul Moore +Paul Nasrat +Paul Oswald +Paul van der Linden +Paulus Schoutsen +Pavel Safronov +Pavithra Eswaramoorthy +Pawel Jasinski +Paweł Szramowski +Pekka Klärck +Peter Gessler +Peter Lisák +Peter Shen +Peter Waller +Petr Viktorin +petr-tik +Phaneendra Chiruvella +Phil Elson +Phil Freo +Phil Pennock +Phil Whelan +Philip Jägenstedt +Philip Molloy +Philippe Ombredanne +Pi Delport +Pierre-Yves Rofes +Pieter Degroote +pip +Prabakaran Kumaresshan +Prabhjyotsing Surjit Singh Sodhi +Prabhu Marappan +Pradyun Gedam +Prashant Sharma +Pratik Mallya +pre-commit-ci[bot] +Preet Thakkar +Preston Holmes +Przemek Wrzos +Pulkit Goyal +q0w +Qiangning Hong +Qiming Xu +qraqras +Quentin Lee +Quentin Pradet +R. David Murray +Rafael Caricio +RAHUL KUMAR +rahulkotargasthi +Ralf Schmitt +Ran Benita +Randy Döring +Razzi Abuissa +rdb +Reece Dunham +Remi Rampin +Rene Dudfield +Riccardo Magliocchetti +Riccardo Schirone +Richard Jones +Richard Si +Ricky Ng-Adam +Rishi +rmorotti +RobberPhex +Robert Collins +Robert McGibbon +Robert Pollak +Robert T. McGibbon +robin elisha robinson +Rodney, Tiara +Roey Berman +Rohan Jain +Roman Bogorodskiy +Roman Donchenko +Romuald Brunet +ronaudinho +Ronny Pfannschmidt +Rory McCann +Ross Brattain +Roy Wellington Ⅳ +RoyS +Ruairidh MacLeod +Russell Keith-Magee +Ryan Shepherd +Ryan Wooden +ryneeverett +Ryuma Asai +S. Guliaev +Sachi King +Sai Sridhar Tarra +Salvatore Rinchiera +sandeepkiran-js +Sander Van Balen +Savio Jomton +schlamar +Scott Kitterman +Sean +seanj +Sebastian Höffner +Sebastian Jordan +Sebastian Schaetz +Segev Finer +SeongSoo Cho +Sepehr Rasouli +sepehrrasooli +Sergey Vasilyev +Seth Michael Larson +Seth Woodworth +Shahar Epstein +Shantanu +shenxianpeng +shireenrao +Shivam Shrey +Shivansh-007 +Shixian Sheng +Shlomi Fish +Shovan Maity +Shubham Nagure +Simeon Visser +Simon Cross +Simon Pichugin +sinoroc +sinscary +snook92 +socketubs +Sorin Sbarnea +Srinivas Nyayapati +Srishti Hegde +Stavros Korokithakis +Stefan Scherfke +Stefano Rivera +Stephan Erb +Stephane Chazelas +Stephen Payne +Stephen Rosen +stepshal +Steve (Gadget) Barnes +Steve Barnes +Steve Dower +Steve Kowalik +Steven Myint +Steven Silvester +stonebig +studioj +Stéphane Bidoul +Stéphane Bidoul (ACSONE) +Stéphane Klein +Sumana Harihareswara +Surbhi Sharma +Sviatoslav Sydorenko +Sviatoslav Sydorenko (Святослав Сидоренко) +Swat009 +Sylvain +S​age Abdullah +Takayuki SHIMIZUKAWA +Taneli Hukkinen +tbeswick +Terrance +Thiago +Thijs Triemstra +Thomas Fenzl +Thomas Grainger +Thomas Guettler +Thomas Johansson +Thomas Kluyver +Thomas Smith +Thomas VINCENT +Tim D. Smith +Tim Gates +Tim Harder +Tim Heap +tim smith +tinruufu +Tobias Hermann +Tom Forbes +Tom Freudenheim +Tom V +Tomas Hrnciar +Tomas Orsava +Tomer Chachamu +Tommi Enenkel | AnB +Tomáš Hrnčiar +Tony Beswick +Tony Narlock +Tony Zhaocheng Tan +TonyBeswick +toonarmycaptain +Toshio Kuratomi +toxinu +Travis Swicegood +Tushar Sadhwani +Tzu-ping Chung +Valentin Haenel +Victor Stinner +victorvpaulo +Vikram - Google +Viktor Szépe +Ville Skyttä +Vinay Sajip +Vincent Fazio +Vincent Philippon +Vinicyus Macedo +Vipul Kumar +Vitaly Babiy +Vladimir Fokow +Vladimir Rutsky +W. Trevor King +Weida Hong +Wil Tan +Wilfred Hughes +William Edwards +William ML Leslie +William T Olson +William Woodruff +Wilson Mo +wim glenn +Winson Luk +Wolfgang Maier +Wu Zhenyu +XAMES3 +Xavier Fernandez +Xianpeng Shen +xoviat +xtreak +YAMAMOTO Takashi +Yash +Yashraj +Yen Chi Hsuan +Yeray Diaz Diaz +Youngkwang Yang +Yoval P +Yu Jian +Yuan Jing Vincent Yan +Yuki Kobayashi +Yusuke Hayashi +Zachary Ware +zackzack38 +Zearin +Zhiping Deng +ziebam +Zvezdan Petkovic +Łukasz Langa +Роман Донченко +Семён Марьясин diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/LICENSE.txt b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..8e7b65eaf628360e6f32f4140fcdd7ec7c2b7077 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-present The pip developers (see AUTHORS.txt file) + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d8b3b56d361cfaa04de6304c96c328f27266f74e --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2012-2021 Eric Larson + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/certifi/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/certifi/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..62b076cdee58ec8f34034141ba0befd9015b0c7e --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/certifi/LICENSE @@ -0,0 +1,20 @@ +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +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 file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/distlib/LICENSE.txt b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/distlib/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..c31ac56d772f7061c8b42f0ee96063b7a94a4859 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/distlib/LICENSE.txt @@ -0,0 +1,284 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.2 2.1.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2.1 2.2 2002 PSF yes + 2.2.2 2.2.1 2002 PSF yes + 2.2.3 2.2.2 2003 PSF yes + 2.3 2.2.2 2002-2003 PSF yes + 2.3.1 2.3 2002-2003 PSF yes + 2.3.2 2.3.1 2002-2003 PSF yes + 2.3.3 2.3.2 2002-2003 PSF yes + 2.3.4 2.3.3 2004 PSF yes + 2.3.5 2.3.4 2005 PSF yes + 2.4 2.3 2004 PSF yes + 2.4.1 2.4 2005 PSF yes + 2.4.2 2.4.1 2005 PSF yes + 2.4.3 2.4.2 2006 PSF yes + 2.4.4 2.4.3 2006 PSF yes + 2.5 2.4 2006 PSF yes + 2.5.1 2.5 2007 PSF yes + 2.5.2 2.5.1 2008 PSF yes + 2.5.3 2.5.2 2008 PSF yes + 2.6 2.5 2008 PSF yes + 2.6.1 2.6 2008 PSF yes + 2.6.2 2.6.1 2009 PSF yes + 2.6.3 2.6.2 2009 PSF yes + 2.6.4 2.6.3 2009 PSF yes + 2.6.5 2.6.4 2010 PSF yes + 3.0 2.6 2008 PSF yes + 3.0.1 3.0 2009 PSF yes + 3.1 3.0.1 2009 PSF yes + 3.1.1 3.1 2009 PSF yes + 3.1.2 3.1 2010 PSF yes + 3.2 3.1 2010 PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +Python Software Foundation; All Rights Reserved" are retained in Python alone or +in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/distro/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/distro/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e06d2081865a766a8668acc12878f98b27fc9ea0 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/distro/LICENSE @@ -0,0 +1,202 @@ +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 {yyyy} {name of copyright owner} + + 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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..f706835ab3a5dd709a6e1f57aee0a94ae1415df6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2013-2026, Kim Davies and contributors. +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/msgpack/COPYING b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/msgpack/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..f067af3aae15a5b494f0eeb15a7064c176e30fd5 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/msgpack/COPYING @@ -0,0 +1,14 @@ +Copyright (C) 2008-2011 INADA Naoki + + 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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6f62d44e4ef733c0e713afcd2371fed7f2b3de67 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made +under the terms of *both* these licenses. diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.APACHE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.APACHE new file mode 100644 index 0000000000000000000000000000000000000000..f433b1a53f5b830a205fd2df78e2b34974656c7b --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.APACHE @@ -0,0 +1,177 @@ + + 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 diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.BSD b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.BSD new file mode 100644 index 0000000000000000000000000000000000000000..42ce7b75c92fb01a3f6ed17eea363f756b7da582 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.BSD @@ -0,0 +1,23 @@ +Copyright (c) Donald Stufft and individual contributors. +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. + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..1bb5a44356f00884a71ceeefd24ded6caaba2418 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE @@ -0,0 +1,17 @@ +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f35fed9191b1142ddaada8a96de4a9461c5d796c --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2010-202x The platformdirs developers + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pygments/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pygments/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..446a1a805c8a949579fb8a9799f2bec7777349dc --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pygments/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2006-2022 by the respective authors (see AUTHORS file). +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. + +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 +OWNER 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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..b0ae9dbc262b5f7ed4cb6f964cfb5eb5c0d0b0be --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Thomas Kluyver + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/requests/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/requests/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..67db8588217f266eb561f75fae738656325deac9 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/requests/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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..b9077766e9b9bdcae49ea5c8fced750ed13ec8f7 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2018, Tzu-ping Chung + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/rich/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/rich/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4415505566f261c802b671426be529a31f914137 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/rich/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Will McGugan + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e859590f886cd78344206af1a8ccb3080d4385e0 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Taneli Hukkinen + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e859590f886cd78344206af1a8ccb3080d4385e0 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Taneli Hukkinen + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/truststore/LICENSE b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/truststore/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..7ec568c1136eb8ec86aa05cf7abacfb54c3e5ead --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/truststore/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Seth Michael Larson + +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/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/urllib3/LICENSE.txt b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/urllib3/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..e6183d0276b26c5b87aecccf8d0d5bcd7b1148d4 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip-26.2.2.dist-info/licenses/src/pip/_vendor/urllib3/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2008-2020 Andrey Petrov and 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/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_parser.py b/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..41b06418805bdfe2b5fba390566aaa4d274f829e --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_parser.py @@ -0,0 +1,793 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Taneli Hukkinen +# Licensed to PSF under a Contributor Agreement. + +from __future__ import annotations + +import sys +from types import MappingProxyType + +from ._re import ( + RE_DATETIME, + RE_LOCALTIME, + RE_NUMBER, + match_to_datetime, + match_to_localtime, + match_to_number, +) + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Iterable + from typing import IO, Any, Final + + from ._types import Key, ParseFloat, Pos + +# Inline tables/arrays are implemented using recursion. Pathologically +# nested documents cause pure Python to raise RecursionError (which is OK), +# but mypyc binary wheels will crash unrecoverably (not OK). According to +# mypyc docs this will be fixed in the future: +# https://mypyc.readthedocs.io/en/latest/differences_from_python.html#stack-overflows +# Before mypyc's fix is in, recursion needs to be limited by this library. +# Choosing `sys.getrecursionlimit()` as maximum inline table/array nesting +# level, as it allows more nesting than pure Python, but still seems a far +# lower number than where mypyc binaries crash. +MAX_INLINE_NESTING: Final = sys.getrecursionlimit() + +# Pathologically excessive number of parts in a key runs into quadratic +# behavior (e.g. in Flags.is_). +# Even if keys aren't currently parsed using recursion, they name a +# recursive structure, so it makes sense to limit it using getrecursionlimit() +# and RecursionError. +MAX_KEY_PARTS: Final = sys.getrecursionlimit() + +ASCII_CTRL: Final = frozenset(chr(i) for i in range(32)) | frozenset(chr(127)) + +# Neither of these sets include quotation mark or backslash. They are +# currently handled as separate cases in the parser functions. +ILLEGAL_BASIC_STR_CHARS: Final = ASCII_CTRL - frozenset("\t") +ILLEGAL_MULTILINE_BASIC_STR_CHARS: Final = ASCII_CTRL - frozenset("\t\n") + +ILLEGAL_LITERAL_STR_CHARS: Final = ILLEGAL_BASIC_STR_CHARS +ILLEGAL_MULTILINE_LITERAL_STR_CHARS: Final = ILLEGAL_MULTILINE_BASIC_STR_CHARS + +ILLEGAL_COMMENT_CHARS: Final = ILLEGAL_BASIC_STR_CHARS + +TOML_WS: Final = frozenset(" \t") +TOML_WS_AND_NEWLINE: Final = TOML_WS | frozenset("\n") +BARE_KEY_CHARS: Final = frozenset( + "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "-_" +) +KEY_INITIAL_CHARS: Final = BARE_KEY_CHARS | frozenset("\"'") +HEXDIGIT_CHARS: Final = frozenset("abcdef" "ABCDEF" "0123456789") + +BASIC_STR_ESCAPE_REPLACEMENTS: Final = MappingProxyType( + { + "\\b": "\u0008", # backspace + "\\t": "\u0009", # tab + "\\n": "\u000a", # linefeed + "\\f": "\u000c", # form feed + "\\r": "\u000d", # carriage return + "\\e": "\u001b", # escape + '\\"': "\u0022", # quote + "\\\\": "\u005c", # backslash + } +) + + +class DEPRECATED_DEFAULT: + """Sentinel to be used as default arg during deprecation + period of TOMLDecodeError's free-form arguments.""" + + +class TOMLDecodeError(ValueError): + """An error raised if a document is not valid TOML. + + Adds the following attributes to ValueError: + msg: The unformatted error message + doc: The TOML document being parsed + pos: The index of doc where parsing failed + lineno: The line corresponding to pos + colno: The column corresponding to pos + """ + + def __init__( + self, + msg: str | type[DEPRECATED_DEFAULT] = DEPRECATED_DEFAULT, + doc: str | type[DEPRECATED_DEFAULT] = DEPRECATED_DEFAULT, + pos: Pos | type[DEPRECATED_DEFAULT] = DEPRECATED_DEFAULT, + *args: Any, + ): + if ( + args + or not isinstance(msg, str) + or not isinstance(doc, str) + or not isinstance(pos, int) + ): + import warnings + + warnings.warn( + "Free-form arguments for TOMLDecodeError are deprecated. " + "Please set 'msg' (str), 'doc' (str) and 'pos' (int) arguments only.", + DeprecationWarning, + stacklevel=2, + ) + if pos is not DEPRECATED_DEFAULT: + args = pos, *args + if doc is not DEPRECATED_DEFAULT: + args = doc, *args + if msg is not DEPRECATED_DEFAULT: + args = msg, *args + ValueError.__init__(self, *args) + return + + lineno = doc.count("\n", 0, pos) + 1 + if lineno == 1: + colno = pos + 1 + else: + colno = pos - doc.rindex("\n", 0, pos) + + if pos >= len(doc): + coord_repr = "end of document" + else: + coord_repr = f"line {lineno}, column {colno}" + errmsg = f"{msg} (at {coord_repr})" + ValueError.__init__(self, errmsg) + + self.msg = msg + self.doc = doc + self.pos = pos + self.lineno = lineno + self.colno = colno + + +def load(__fp: IO[bytes], *, parse_float: ParseFloat = float) -> dict[str, Any]: + """Parse TOML from a binary file object.""" + b = __fp.read() + try: + s = b.decode() + except AttributeError: + raise TypeError( + "File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`" + ) from None + return loads(s, parse_float=parse_float) + + +def loads(__s: str, *, parse_float: ParseFloat = float) -> dict[str, Any]: + """Parse TOML from a string.""" + + # The spec allows converting "\r\n" to "\n", even in string + # literals. Let's do so to simplify parsing. + try: + src = __s.replace("\r\n", "\n") + except (AttributeError, TypeError): + raise TypeError( + f"Expected str object, not '{type(__s).__qualname__}'" + ) from None + pos = 0 + out = Output() + header: Key = () + parse_float = make_safe_parse_float(parse_float) + + # Parse one statement at a time + # (typically means one line in TOML source) + while True: + # 1. Skip line leading whitespace + pos = skip_chars(src, pos, TOML_WS) + + # 2. Parse rules. Expect one of the following: + # - end of file + # - end of line + # - comment + # - key/value pair + # - append dict to list (and move to its namespace) + # - create dict (and move to its namespace) + # Skip trailing whitespace when applicable. + try: + char = src[pos] + except IndexError: + break + if char == "\n": + pos += 1 + continue + if char in KEY_INITIAL_CHARS: + pos = key_value_rule(src, pos, out, header, parse_float) + pos = skip_chars(src, pos, TOML_WS) + elif char == "[": + try: + second_char: str | None = src[pos + 1] + except IndexError: + second_char = None + out.flags.finalize_pending() + if second_char == "[": + pos, header = create_list_rule(src, pos, out) + else: + pos, header = create_dict_rule(src, pos, out) + pos = skip_chars(src, pos, TOML_WS) + elif char != "#": + raise TOMLDecodeError("Invalid statement", src, pos) + + # 3. Skip comment + pos = skip_comment(src, pos) + + # 4. Expect end of line or end of file + try: + char = src[pos] + except IndexError: + break + if char != "\n": + raise TOMLDecodeError( + "Expected newline or end of document after a statement", src, pos + ) + pos += 1 + + return out.data.dict + + +class Flags: + """Flags that map to parsed keys/namespaces.""" + + # Marks an immutable namespace (inline array or inline table). + FROZEN: Final = 0 + # Marks a nest that has been explicitly created and can no longer + # be opened using the "[table]" syntax. + EXPLICIT_NEST: Final = 1 + + def __init__(self) -> None: + self._flags: dict[str, dict[Any, Any]] = {} + self._pending_flags: set[tuple[Key, int]] = set() + + def add_pending(self, key: Key, flag: int) -> None: + self._pending_flags.add((key, flag)) + + def finalize_pending(self) -> None: + for key, flag in self._pending_flags: + self.set(key, flag, recursive=False) + self._pending_flags.clear() + + def unset_all(self, key: Key) -> None: + cont = self._flags + for k in key[:-1]: + if k not in cont: + return + cont = cont[k]["nested"] + cont.pop(key[-1], None) + + def set(self, key: Key, flag: int, *, recursive: bool) -> None: # noqa: A003 + cont = self._flags + key_parent, key_stem = key[:-1], key[-1] + for k in key_parent: + if k not in cont: + cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + if key_stem not in cont: + cont[key_stem] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont[key_stem]["recursive_flags" if recursive else "flags"].add(flag) + + def is_(self, key: Key, flag: int) -> bool: + if not key: + return False # document root has no flags + cont = self._flags + for k in key[:-1]: + if k not in cont: + return False + inner_cont = cont[k] + if flag in inner_cont["recursive_flags"]: + return True + cont = inner_cont["nested"] + key_stem = key[-1] + if key_stem in cont: + inner_cont = cont[key_stem] + return flag in inner_cont["flags"] or flag in inner_cont["recursive_flags"] + return False + + +class NestedDict: + def __init__(self) -> None: + # The parsed content of the TOML document + self.dict: dict[str, Any] = {} + + def get_or_create_nest( + self, + key: Key, + *, + access_lists: bool = True, + ) -> dict[str, Any]: + cont: Any = self.dict + for k in key: + if k not in cont: + cont[k] = {} + cont = cont[k] + if access_lists and isinstance(cont, list): + cont = cont[-1] + if not isinstance(cont, dict): + raise KeyError("There is no nest behind this key") + return cont # type: ignore[no-any-return] + + def append_nest_to_list(self, key: Key) -> None: + cont = self.get_or_create_nest(key[:-1]) + last_key = key[-1] + if last_key in cont: + list_ = cont[last_key] + if not isinstance(list_, list): + raise KeyError("An object other than list found behind this key") + list_.append({}) + else: + cont[last_key] = [{}] + + +class Output: + def __init__(self) -> None: + self.data = NestedDict() + self.flags = Flags() + + +def skip_chars(src: str, pos: Pos, chars: Iterable[str]) -> Pos: + try: + while src[pos] in chars: + pos += 1 + except IndexError: + pass + return pos + + +def skip_until( + src: str, + pos: Pos, + expect: str, + *, + error_on: frozenset[str], + error_on_eof: bool, +) -> Pos: + try: + new_pos = src.index(expect, pos) + except ValueError: + new_pos = len(src) + if error_on_eof: + raise TOMLDecodeError(f"Expected {expect!r}", src, new_pos) from None + + if not error_on.isdisjoint(src[pos:new_pos]): + while src[pos] not in error_on: + pos += 1 + raise TOMLDecodeError(f"Found invalid character {src[pos]!r}", src, pos) + return new_pos + + +def skip_comment(src: str, pos: Pos) -> Pos: + try: + char: str | None = src[pos] + except IndexError: + char = None + if char == "#": + return skip_until( + src, pos + 1, "\n", error_on=ILLEGAL_COMMENT_CHARS, error_on_eof=False + ) + return pos + + +def skip_comments_and_array_ws(src: str, pos: Pos) -> Pos: + while True: + pos_before_skip = pos + pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) + pos = skip_comment(src, pos) + if pos == pos_before_skip: + return pos + + +def create_dict_rule(src: str, pos: Pos, out: Output) -> tuple[Pos, Key]: + pos += 1 # Skip "[" + pos = skip_chars(src, pos, TOML_WS) + pos, key = parse_key(src, pos) + + if out.flags.is_(key, Flags.EXPLICIT_NEST) or out.flags.is_(key, Flags.FROZEN): + raise TOMLDecodeError(f"Cannot declare {key} twice", src, pos) + out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) + try: + out.data.get_or_create_nest(key) + except KeyError: + raise TOMLDecodeError("Cannot overwrite a value", src, pos) from None + + if not src.startswith("]", pos): + raise TOMLDecodeError( + "Expected ']' at the end of a table declaration", src, pos + ) + return pos + 1, key + + +def create_list_rule(src: str, pos: Pos, out: Output) -> tuple[Pos, Key]: + pos += 2 # Skip "[[" + pos = skip_chars(src, pos, TOML_WS) + pos, key = parse_key(src, pos) + + if out.flags.is_(key, Flags.FROZEN): + raise TOMLDecodeError(f"Cannot mutate immutable namespace {key}", src, pos) + # Free the namespace now that it points to another empty list item... + out.flags.unset_all(key) + # ...but this key precisely is still prohibited from table declaration + out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) + try: + out.data.append_nest_to_list(key) + except KeyError: + raise TOMLDecodeError("Cannot overwrite a value", src, pos) from None + + if not src.startswith("]]", pos): + raise TOMLDecodeError( + "Expected ']]' at the end of an array declaration", src, pos + ) + return pos + 2, key + + +def key_value_rule( + src: str, pos: Pos, out: Output, header: Key, parse_float: ParseFloat +) -> Pos: + pos, key, value = parse_key_value_pair(src, pos, parse_float, nest_lvl=0) + key_parent, key_stem = key[:-1], key[-1] + abs_key_parent = header + key_parent + + relative_path_cont_keys = (header + key[:i] for i in range(1, len(key))) + for cont_key in relative_path_cont_keys: + # Check that dotted key syntax does not redefine an existing table + if out.flags.is_(cont_key, Flags.EXPLICIT_NEST): + raise TOMLDecodeError(f"Cannot redefine namespace {cont_key}", src, pos) + # Containers in the relative path can't be opened with the table syntax or + # dotted key/value syntax in following table sections. + out.flags.add_pending(cont_key, Flags.EXPLICIT_NEST) + + if out.flags.is_(abs_key_parent, Flags.FROZEN): + raise TOMLDecodeError( + f"Cannot mutate immutable namespace {abs_key_parent}", src, pos + ) + + try: + nest = out.data.get_or_create_nest(abs_key_parent) + except KeyError: + raise TOMLDecodeError("Cannot overwrite a value", src, pos) from None + if key_stem in nest: + raise TOMLDecodeError("Cannot overwrite a value", src, pos) + # Mark inline table and array namespaces recursively immutable + if isinstance(value, (dict, list)): + out.flags.set(header + key, Flags.FROZEN, recursive=True) + nest[key_stem] = value + return pos + + +def parse_key_value_pair( + src: str, pos: Pos, parse_float: ParseFloat, nest_lvl: int +) -> tuple[Pos, Key, Any]: + pos, key = parse_key(src, pos) + try: + char: str | None = src[pos] + except IndexError: + char = None + if char != "=": + raise TOMLDecodeError("Expected '=' after a key in a key/value pair", src, pos) + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + pos, value = parse_value(src, pos, parse_float, nest_lvl) + return pos, key, value + + +def parse_key(src: str, pos: Pos) -> tuple[Pos, Key]: + pos, key_part = parse_key_part(src, pos) + key: Key = (key_part,) + pos = skip_chars(src, pos, TOML_WS) + while True: + try: + char: str | None = src[pos] + except IndexError: + char = None + if char != ".": + return pos, key + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + pos, key_part = parse_key_part(src, pos) + key += (key_part,) + if len(key) > MAX_KEY_PARTS: + raise RecursionError( + f"TOML key has more than the allowed {MAX_KEY_PARTS} parts" + ) + pos = skip_chars(src, pos, TOML_WS) + + +def parse_key_part(src: str, pos: Pos) -> tuple[Pos, str]: + try: + char: str | None = src[pos] + except IndexError: + char = None + if char in BARE_KEY_CHARS: + start_pos = pos + pos = skip_chars(src, pos, BARE_KEY_CHARS) + return pos, src[start_pos:pos] + if char == "'": + return parse_literal_str(src, pos) + if char == '"': + return parse_one_line_basic_str(src, pos) + raise TOMLDecodeError("Invalid initial character for a key part", src, pos) + + +def parse_one_line_basic_str(src: str, pos: Pos) -> tuple[Pos, str]: + pos += 1 + return parse_basic_str(src, pos, multiline=False) + + +def parse_array( + src: str, pos: Pos, parse_float: ParseFloat, nest_lvl: int +) -> tuple[Pos, list[Any]]: + pos += 1 + array: list[Any] = [] + + pos = skip_comments_and_array_ws(src, pos) + if src.startswith("]", pos): + return pos + 1, array + while True: + pos, val = parse_value(src, pos, parse_float, nest_lvl) + array.append(val) + pos = skip_comments_and_array_ws(src, pos) + + c = src[pos : pos + 1] + if c == "]": + return pos + 1, array + if c != ",": + raise TOMLDecodeError("Unclosed array", src, pos) + pos += 1 + + pos = skip_comments_and_array_ws(src, pos) + if src.startswith("]", pos): + return pos + 1, array + + +def parse_inline_table( + src: str, pos: Pos, parse_float: ParseFloat, nest_lvl: int +) -> tuple[Pos, dict[str, Any]]: + pos += 1 + nested_dict = NestedDict() + flags = Flags() + + pos = skip_comments_and_array_ws(src, pos) + if src.startswith("}", pos): + return pos + 1, nested_dict.dict + while True: + pos, key, value = parse_key_value_pair(src, pos, parse_float, nest_lvl) + key_parent, key_stem = key[:-1], key[-1] + if flags.is_(key, Flags.FROZEN): + raise TOMLDecodeError(f"Cannot mutate immutable namespace {key}", src, pos) + try: + nest = nested_dict.get_or_create_nest(key_parent, access_lists=False) + except KeyError: + raise TOMLDecodeError("Cannot overwrite a value", src, pos) from None + if key_stem in nest: + raise TOMLDecodeError(f"Duplicate inline table key {key_stem!r}", src, pos) + nest[key_stem] = value + pos = skip_comments_and_array_ws(src, pos) + c = src[pos : pos + 1] + if c == "}": + return pos + 1, nested_dict.dict + if c != ",": + raise TOMLDecodeError("Unclosed inline table", src, pos) + pos += 1 + pos = skip_comments_and_array_ws(src, pos) + if src.startswith("}", pos): + return pos + 1, nested_dict.dict + if isinstance(value, (dict, list)): + flags.set(key, Flags.FROZEN, recursive=True) + + +def parse_basic_str_escape( + src: str, pos: Pos, *, multiline: bool = False +) -> tuple[Pos, str]: + escape_id = src[pos : pos + 2] + pos += 2 + if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}: + # Skip whitespace until next non-whitespace character or end of + # the doc. Error if non-whitespace is found before newline. + if escape_id != "\\\n": + pos = skip_chars(src, pos, TOML_WS) + try: + char = src[pos] + except IndexError: + return pos, "" + if char != "\n": + raise TOMLDecodeError("Unescaped '\\' in a string", src, pos) + pos += 1 + pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) + return pos, "" + if escape_id == "\\x": + return parse_hex_char(src, pos, 2) + if escape_id == "\\u": + return parse_hex_char(src, pos, 4) + if escape_id == "\\U": + return parse_hex_char(src, pos, 8) + try: + return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id] + except KeyError: + raise TOMLDecodeError("Unescaped '\\' in a string", src, pos) from None + + +def parse_basic_str_escape_multiline(src: str, pos: Pos) -> tuple[Pos, str]: + return parse_basic_str_escape(src, pos, multiline=True) + + +def parse_hex_char(src: str, pos: Pos, hex_len: int) -> tuple[Pos, str]: + hex_str = src[pos : pos + hex_len] + if len(hex_str) != hex_len or not HEXDIGIT_CHARS.issuperset(hex_str): + raise TOMLDecodeError("Invalid hex value", src, pos) + pos += hex_len + hex_int = int(hex_str, 16) + if not is_unicode_scalar_value(hex_int): + raise TOMLDecodeError( + "Escaped character is not a Unicode scalar value", src, pos + ) + return pos, chr(hex_int) + + +def parse_literal_str(src: str, pos: Pos) -> tuple[Pos, str]: + pos += 1 # Skip starting apostrophe + start_pos = pos + pos = skip_until( + src, pos, "'", error_on=ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True + ) + return pos + 1, src[start_pos:pos] # Skip ending apostrophe + + +def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> tuple[Pos, str]: + pos += 3 + if src.startswith("\n", pos): + pos += 1 + + if literal: + delim = "'" + end_pos = skip_until( + src, + pos, + "'''", + error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS, + error_on_eof=True, + ) + result = src[pos:end_pos] + pos = end_pos + 3 + else: + delim = '"' + pos, result = parse_basic_str(src, pos, multiline=True) + + # Add at maximum two extra apostrophes/quotes if the end sequence + # is 4 or 5 chars long instead of just 3. + if not src.startswith(delim, pos): + return pos, result + pos += 1 + if not src.startswith(delim, pos): + return pos, result + delim + pos += 1 + return pos, result + (delim * 2) + + +def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> tuple[Pos, str]: + if multiline: + error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS + parse_escapes = parse_basic_str_escape_multiline + else: + error_on = ILLEGAL_BASIC_STR_CHARS + parse_escapes = parse_basic_str_escape + result = "" + start_pos = pos + while True: + try: + char = src[pos] + except IndexError: + raise TOMLDecodeError("Unterminated string", src, pos) from None + if char == '"': + if not multiline: + return pos + 1, result + src[start_pos:pos] + if src.startswith('"""', pos): + return pos + 3, result + src[start_pos:pos] + pos += 1 + continue + if char == "\\": + result += src[start_pos:pos] + pos, parsed_escape = parse_escapes(src, pos) + result += parsed_escape + start_pos = pos + continue + if char in error_on: + raise TOMLDecodeError(f"Illegal character {char!r}", src, pos) + pos += 1 + + +def parse_value( + src: str, pos: Pos, parse_float: ParseFloat, nest_lvl: int +) -> tuple[Pos, Any]: + if nest_lvl > MAX_INLINE_NESTING: + # Pure Python should have raised RecursionError already. + # This ensures mypyc binaries eventually do the same. + raise RecursionError( # pragma: no cover + "TOML inline arrays/tables are nested more than the allowed" + f" {MAX_INLINE_NESTING} levels" + ) + + try: + char: str | None = src[pos] + except IndexError: + char = None + + # IMPORTANT: order conditions based on speed of checking and likelihood + + # Basic strings + if char == '"': + if src.startswith('"""', pos): + return parse_multiline_str(src, pos, literal=False) + return parse_one_line_basic_str(src, pos) + + # Literal strings + if char == "'": + if src.startswith("'''", pos): + return parse_multiline_str(src, pos, literal=True) + return parse_literal_str(src, pos) + + # Booleans + if char == "t": + if src.startswith("true", pos): + return pos + 4, True + if char == "f": + if src.startswith("false", pos): + return pos + 5, False + + # Arrays + if char == "[": + return parse_array(src, pos, parse_float, nest_lvl + 1) + + # Inline tables + if char == "{": + return parse_inline_table(src, pos, parse_float, nest_lvl + 1) + + # Dates and times + datetime_match = RE_DATETIME.match(src, pos) + if datetime_match: + try: + datetime_obj = match_to_datetime(datetime_match) + except ValueError as e: + raise TOMLDecodeError("Invalid date or datetime", src, pos) from e + return datetime_match.end(), datetime_obj + localtime_match = RE_LOCALTIME.match(src, pos) + if localtime_match: + return localtime_match.end(), match_to_localtime(localtime_match) + + # Integers and "normal" floats. + # The regex will greedily match any type starting with a decimal + # char, so needs to be located after handling of dates and times. + number_match = RE_NUMBER.match(src, pos) + if number_match: + return number_match.end(), match_to_number(number_match, parse_float) + + # Special floats + first_three = src[pos : pos + 3] + if first_three in {"inf", "nan"}: + return pos + 3, parse_float(first_three) + first_four = src[pos : pos + 4] + if first_four in {"-inf", "+inf", "-nan", "+nan"}: + return pos + 4, parse_float(first_four) + + raise TOMLDecodeError("Invalid value", src, pos) + + +def is_unicode_scalar_value(codepoint: int) -> bool: + return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111) + + +def make_safe_parse_float(parse_float: ParseFloat) -> ParseFloat: + """A decorator to make `parse_float` safe. + + `parse_float` must not return dicts or lists, because these types + would be mixed with parsed TOML tables and arrays, thus confusing + the parser. The returned decorated callable raises `ValueError` + instead of returning illegal types. + """ + # The default `float` callable never returns illegal types. Optimize it. + if parse_float is float: + return float + + def safe_parse_float(float_str: str) -> Any: + float_value = parse_float(float_str) + if isinstance(float_value, (dict, list)): + raise ValueError("parse_float must not return dicts or lists") + return float_value + + return safe_parse_float diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_re.py b/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_re.py new file mode 100644 index 0000000000000000000000000000000000000000..fc374ed63d3e3742a97134349fc25b14223ab57b --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_re.py @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Taneli Hukkinen +# Licensed to PSF under a Contributor Agreement. + +from __future__ import annotations + +from datetime import date, datetime, time, timedelta, timezone, tzinfo +from functools import lru_cache +import re + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Any, Final + + from ._types import ParseFloat + +_TIME_RE_STR: Final = r""" +([01][0-9]|2[0-3]) # hours +:([0-5][0-9]) # minutes +(?: + :([0-5][0-9]) # optional seconds + (?:\.([0-9]{1,6})[0-9]*)? # optional fractions of a second +)? +""" + +RE_NUMBER: Final = re.compile( + r""" +0 +(?: + x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex + | + b[01](?:_?[01])* # bin + | + o[0-7](?:_?[0-7])* # oct +) +| +[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part +(?P + (?:\.[0-9](?:_?[0-9])*)? # optional fractional part + (?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part +) +""", + flags=re.VERBOSE, +) +RE_LOCALTIME: Final = re.compile(_TIME_RE_STR, flags=re.VERBOSE) +RE_DATETIME: Final = re.compile( + rf""" +([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27 +(?: + [Tt ] + {_TIME_RE_STR} + (?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset +)? +""", + flags=re.VERBOSE, +) + + +def match_to_datetime(match: re.Match[str]) -> datetime | date: + """Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`. + + Raises ValueError if the match does not correspond to a valid date + or datetime. + """ + ( + year_str, + month_str, + day_str, + hour_str, + minute_str, + sec_str, + micros_str, + zulu_time, + offset_sign_str, + offset_hour_str, + offset_minute_str, + ) = match.groups() + year, month, day = int(year_str), int(month_str), int(day_str) + if hour_str is None: + return date(year, month, day) + hour, minute = int(hour_str), int(minute_str) + sec = int(sec_str) if sec_str else 0 + micros = int(micros_str.ljust(6, "0")) if micros_str else 0 + if offset_sign_str: + tz: tzinfo | None = cached_tz( + offset_hour_str, offset_minute_str, offset_sign_str + ) + elif zulu_time: + tz = timezone.utc + else: # local date-time + tz = None + return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz) + + +# No need to limit cache size. This is only ever called on input +# that matched RE_DATETIME, so there is an implicit bound of +# 24 (hours) * 60 (minutes) * 2 (offset direction) = 2880. +@lru_cache(maxsize=None) +def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone: + sign = 1 if sign_str == "+" else -1 + return timezone( + timedelta( + hours=sign * int(hour_str), + minutes=sign * int(minute_str), + ) + ) + + +def match_to_localtime(match: re.Match[str]) -> time: + hour_str, minute_str, sec_str, micros_str = match.groups() + sec = int(sec_str) if sec_str else 0 + micros = int(micros_str.ljust(6, "0")) if micros_str else 0 + return time(int(hour_str), int(minute_str), sec, micros) + + +def match_to_number(match: re.Match[str], parse_float: ParseFloat) -> Any: + if match.group("floatpart"): + return parse_float(match.group()) + return int(match.group(), 0) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_types.py b/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_types.py new file mode 100644 index 0000000000000000000000000000000000000000..d949412e03b29d70592c7721fe747e5085c2e280 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/tomli/_types.py @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2021 Taneli Hukkinen +# Licensed to PSF under a Contributor Agreement. + +from typing import Any, Callable, Tuple + +# Type annotations +ParseFloat = Callable[[str], Any] +Key = Tuple[str, ...] +Pos = int diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/tomli/py.typed b/hug/lib/python3.12/site-packages/pip/_vendor/tomli/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..7632ecf77545c5e5501cb3fc5719df0761104ca2 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/tomli/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/LICENSE b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..e859590f886cd78344206af1a8ccb3080d4385e0 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Taneli Hukkinen + +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/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__init__.py b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6349c1f05bc67de0f06b71b3021143c46f583b11 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__init__.py @@ -0,0 +1,4 @@ +__all__ = ("dumps", "dump") +__version__ = "1.2.0" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT + +from pip._vendor.tomli_w._writer import dump, dumps diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..918825bb8e57711ea135654efdac0ed358bb204d Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/_writer.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/_writer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..977dfc257ec3a7b5606722a24594e74d5d285e71 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/_writer.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/_writer.py b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/_writer.py new file mode 100644 index 0000000000000000000000000000000000000000..b1acd3f26bae62f5d64d9c97436acaab26a0faee --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/_writer.py @@ -0,0 +1,229 @@ +from __future__ import annotations + +from collections.abc import Mapping +from datetime import date, datetime, time +from types import MappingProxyType + +TYPE_CHECKING = False +if TYPE_CHECKING: + from collections.abc import Generator + from decimal import Decimal + from typing import IO, Any, Final + +ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127)) +ILLEGAL_BASIC_STR_CHARS = frozenset('"\\') | ASCII_CTRL - frozenset("\t") +BARE_KEY_CHARS = frozenset( + "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "-_" +) +ARRAY_TYPES = (list, tuple) +MAX_LINE_LENGTH = 100 + +COMPACT_ESCAPES = MappingProxyType( + { + "\u0008": "\\b", # backspace + "\u000A": "\\n", # linefeed + "\u000C": "\\f", # form feed + "\u000D": "\\r", # carriage return + "\u0022": '\\"', # quote + "\u005C": "\\\\", # backslash + } +) + + +class Context: + def __init__(self, allow_multiline: bool, indent: int): + if indent < 0: + raise ValueError("Indent width must be non-negative") + self.allow_multiline: Final = allow_multiline + # cache rendered inline tables (mapping from object id to rendered inline table) + self.inline_table_cache: Final[dict[int, str]] = {} + self.indent_str: Final = " " * indent + + +def dump( + obj: Mapping[str, Any], + fp: IO[bytes], + /, + *, + multiline_strings: bool = False, + indent: int = 4, +) -> None: + ctx = Context(multiline_strings, indent) + for chunk in gen_table_chunks(obj, ctx, name=""): + fp.write(chunk.encode()) + + +def dumps( + obj: Mapping[str, Any], /, *, multiline_strings: bool = False, indent: int = 4 +) -> str: + ctx = Context(multiline_strings, indent) + return "".join(gen_table_chunks(obj, ctx, name="")) + + +def gen_table_chunks( + table: Mapping[str, Any], + ctx: Context, + *, + name: str, + inside_aot: bool = False, +) -> Generator[str, None, None]: + yielded = False + literals = [] + tables: list[tuple[str, Any, bool]] = [] # => [(key, value, inside_aot)] + for k, v in table.items(): + if isinstance(v, Mapping): + tables.append((k, v, False)) + elif is_aot(v) and not all(is_suitable_inline_table(t, ctx) for t in v): + tables.extend((k, t, True) for t in v) + else: + literals.append((k, v)) + + if inside_aot or name and (literals or not tables): + yielded = True + yield f"[[{name}]]\n" if inside_aot else f"[{name}]\n" + + if literals: + yielded = True + for k, v in literals: + yield f"{format_key_part(k)} = {format_literal(v, ctx)}\n" + + for k, v, in_aot in tables: + if yielded: + yield "\n" + else: + yielded = True + key_part = format_key_part(k) + display_name = f"{name}.{key_part}" if name else key_part + yield from gen_table_chunks(v, ctx, name=display_name, inside_aot=in_aot) + + +def format_literal(obj: object, ctx: Context, *, nest_level: int = 0) -> str: + if isinstance(obj, bool): + return "true" if obj else "false" + if isinstance(obj, (int, float, date, datetime)): + return str(obj) + if isinstance(obj, time): + if obj.tzinfo: + raise ValueError("TOML does not support offset times") + return str(obj) + if isinstance(obj, str): + return format_string(obj, allow_multiline=ctx.allow_multiline) + if isinstance(obj, ARRAY_TYPES): + return format_inline_array(obj, ctx, nest_level) + if isinstance(obj, Mapping): + return format_inline_table(obj, ctx) + + # Lazy import to improve module import time + from decimal import Decimal + + if isinstance(obj, Decimal): + return format_decimal(obj) + raise TypeError( + f"Object of type '{type(obj).__qualname__}' is not TOML serializable" + ) + + +def format_decimal(obj: Decimal) -> str: + if obj.is_nan(): + return "nan" + if obj.is_infinite(): + return "-inf" if obj.is_signed() else "inf" + dec_str = str(obj).lower() + return dec_str if "." in dec_str or "e" in dec_str else dec_str + ".0" + + +def format_inline_table(obj: Mapping, ctx: Context) -> str: + # check cache first + obj_id = id(obj) + if obj_id in ctx.inline_table_cache: + return ctx.inline_table_cache[obj_id] + + if not obj: + rendered = "{}" + else: + rendered = ( + "{ " + + ", ".join( + f"{format_key_part(k)} = {format_literal(v, ctx)}" + for k, v in obj.items() + ) + + " }" + ) + ctx.inline_table_cache[obj_id] = rendered + return rendered + + +def format_inline_array(obj: tuple | list, ctx: Context, nest_level: int) -> str: + if not obj: + return "[]" + item_indent = ctx.indent_str * (1 + nest_level) + closing_bracket_indent = ctx.indent_str * nest_level + return ( + "[\n" + + ",\n".join( + item_indent + format_literal(item, ctx, nest_level=nest_level + 1) + for item in obj + ) + + f",\n{closing_bracket_indent}]" + ) + + +def format_key_part(part: str) -> str: + try: + only_bare_key_chars = BARE_KEY_CHARS.issuperset(part) + except TypeError: + raise TypeError( + f"Invalid mapping key '{part}' of type '{type(part).__qualname__}'." + " A string is required." + ) from None + + if part and only_bare_key_chars: + return part + return format_string(part, allow_multiline=False) + + +def format_string(s: str, *, allow_multiline: bool) -> str: + do_multiline = allow_multiline and "\n" in s + if do_multiline: + result = '"""\n' + s = s.replace("\r\n", "\n") + else: + result = '"' + + pos = seq_start = 0 + while True: + try: + char = s[pos] + except IndexError: + result += s[seq_start:pos] + if do_multiline: + return result + '"""' + return result + '"' + if char in ILLEGAL_BASIC_STR_CHARS: + result += s[seq_start:pos] + if char in COMPACT_ESCAPES: + if do_multiline and char == "\n": + result += "\n" + else: + result += COMPACT_ESCAPES[char] + else: + result += "\\u" + hex(ord(char))[2:].rjust(4, "0") + seq_start = pos + 1 + pos += 1 + + +def is_aot(obj: Any) -> bool: + """Decides if an object behaves as an array of tables (i.e. a nonempty list + of dicts).""" + return bool( + isinstance(obj, ARRAY_TYPES) + and obj + and all(isinstance(v, Mapping) for v in obj) + ) + + +def is_suitable_inline_table(obj: Mapping, ctx: Context) -> bool: + """Use heuristics to decide if the inline-style representation is a good + choice for a given table.""" + rendered_inline = f"{ctx.indent_str}{format_inline_table(obj, ctx)}," + return len(rendered_inline) <= MAX_LINE_LENGTH and "\n" not in rendered_inline diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/py.typed b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..7632ecf77545c5e5501cb3fc5719df0761104ca2 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/tomli_w/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/LICENSE b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..7ec568c1136eb8ec86aa05cf7abacfb54c3e5ead --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Seth Michael Larson + +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/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__init__.py b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0166459b40f13bf0f130dc0230af4cb52ea275f9 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__init__.py @@ -0,0 +1,36 @@ +"""Verify certificates using native system trust stores""" + +import sys as _sys + +if _sys.version_info < (3, 10): + raise ImportError("truststore requires Python 3.10 or later") + +# Detect Python runtimes which don't implement SSLObject.get_unverified_chain() API +# This API only became public in Python 3.13 but was available in CPython and PyPy since 3.10. +if _sys.version_info < (3, 13) and _sys.implementation.name not in ("cpython", "pypy"): + try: + import ssl as _ssl + except ImportError: + raise ImportError("truststore requires the 'ssl' module") + else: + _sslmem = _ssl.MemoryBIO() + _sslobj = _ssl.create_default_context().wrap_bio( + _sslmem, + _sslmem, + ) + try: + while not hasattr(_sslobj, "get_unverified_chain"): + _sslobj = _sslobj._sslobj # type: ignore[attr-defined] + except AttributeError: + raise ImportError( + "truststore requires peer certificate chain APIs to be available" + ) from None + + del _ssl, _sslobj, _sslmem # noqa: F821 + +from ._api import SSLContext, extract_from_ssl, inject_into_ssl # noqa: E402 + +del _api, _sys # type: ignore[name-defined] # noqa: F821 + +__all__ = ["SSLContext", "inject_into_ssl", "extract_from_ssl"] +__version__ = "0.10.4" diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37a696c117be66620ebdff2d24779f9a045a52bf Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44d95363e98eed44f6d3d9bf1b3196cf8bcb0dae Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cbf13d777b174e72c3386837b913b90c2867235 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a4ad072b14d0e86afe93150c6ef2632cb2a8463 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c0a9dff34d336b1986620efdd4f96d76b26936f Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1832ddafb317d350fc2b6715cc91bcd1852ff36 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_api.py b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_api.py new file mode 100644 index 0000000000000000000000000000000000000000..a54ff9f22cd28eddfdb68fbf409c927b2858385d --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_api.py @@ -0,0 +1,341 @@ +import contextlib +import os +import platform +import socket +import ssl +import sys +import threading +import typing + +import _ssl + +from ._ssl_constants import ( + _original_SSLContext, + _original_super_SSLContext, + _truststore_SSLContext_dunder_class, + _truststore_SSLContext_super_class, +) + +if platform.system() == "Windows": + from ._windows import _configure_context, _verify_peercerts_impl +elif platform.system() == "Darwin": + from ._macos import _configure_context, _verify_peercerts_impl +else: + from ._openssl import _configure_context, _verify_peercerts_impl + +if typing.TYPE_CHECKING: + from typing_extensions import Buffer + +# From typeshed/stdlib/ssl.pyi +_StrOrBytesPath: typing.TypeAlias = str | bytes | os.PathLike[str] | os.PathLike[bytes] +_PasswordType: typing.TypeAlias = str | bytes | typing.Callable[[], str | bytes] + + +def inject_into_ssl() -> None: + """Injects the :class:`truststore.SSLContext` into the ``ssl`` + module by replacing :class:`ssl.SSLContext`. + """ + setattr(ssl, "SSLContext", SSLContext) + # urllib3 holds on to its own reference of ssl.SSLContext + # so we need to replace that reference too. + try: + import pip._vendor.urllib3.util.ssl_ as urllib3_ssl + + setattr(urllib3_ssl, "SSLContext", SSLContext) + except ImportError: + pass + + # requests starting with 2.32.0 added a preloaded SSL context to improve concurrent performance; + # this unfortunately leads to a RecursionError, which can be avoided by patching the preloaded SSL context with + # the truststore patched instance + # also see https://github.com/psf/requests/pull/6667 + try: + from pip._vendor.requests import adapters as requests_adapters + + preloaded_context = getattr(requests_adapters, "_preloaded_ssl_context", None) + if preloaded_context is not None: + setattr( + requests_adapters, + "_preloaded_ssl_context", + SSLContext(ssl.PROTOCOL_TLS_CLIENT), + ) + except ImportError: + pass + + +def extract_from_ssl() -> None: + """Restores the :class:`ssl.SSLContext` class to its original state""" + setattr(ssl, "SSLContext", _original_SSLContext) + try: + import pip._vendor.urllib3.util.ssl_ as urllib3_ssl + + urllib3_ssl.SSLContext = _original_SSLContext + except ImportError: + pass + + +class SSLContext(_truststore_SSLContext_super_class): # type: ignore[misc] + """SSLContext API that uses system certificates on all platforms""" + + @property # type: ignore[misc] + def __class__(self) -> type: + # Dirty hack to get around isinstance() checks + # for ssl.SSLContext instances in aiohttp/trustme + # when using non-CPython implementations. + return _truststore_SSLContext_dunder_class or SSLContext + + def __init__(self, protocol: int = None) -> None: # type: ignore[assignment] + self._ctx = _original_SSLContext(protocol) + self._ctx_lock = threading.Lock() + + class TruststoreSSLObject(ssl.SSLObject): + # This object exists because wrap_bio() doesn't + # immediately do the handshake so we need to do + # certificate verifications after SSLObject.do_handshake() + + def do_handshake(self) -> None: + ret = super().do_handshake() + _verify_peercerts(self, server_hostname=self.server_hostname) + return ret + + self._ctx.sslobject_class = TruststoreSSLObject + + def wrap_socket( + self, + sock: socket.socket, + server_side: bool = False, + do_handshake_on_connect: bool = True, + suppress_ragged_eofs: bool = True, + server_hostname: str | None = None, + session: ssl.SSLSession | None = None, + ) -> ssl.SSLSocket: + + # We need to lock around the .__enter__() + # but we don't need to lock within the + # context manager, so we need to expand the + # syntactic sugar of the `with` statement. + with contextlib.ExitStack() as stack: + with self._ctx_lock: + stack.enter_context(_configure_context(self._ctx)) + + ssl_sock = self._ctx.wrap_socket( + sock, + server_side=server_side, + server_hostname=server_hostname, + do_handshake_on_connect=do_handshake_on_connect, + suppress_ragged_eofs=suppress_ragged_eofs, + session=session, + ) + try: + _verify_peercerts(ssl_sock, server_hostname=server_hostname) + except Exception: + ssl_sock.close() + raise + return ssl_sock + + def wrap_bio( + self, + incoming: ssl.MemoryBIO, + outgoing: ssl.MemoryBIO, + server_side: bool = False, + server_hostname: str | None = None, + session: ssl.SSLSession | None = None, + ) -> ssl.SSLObject: + with _configure_context(self._ctx): + ssl_obj = self._ctx.wrap_bio( + incoming, + outgoing, + server_hostname=server_hostname, + server_side=server_side, + session=session, + ) + return ssl_obj + + def load_verify_locations( + self, + cafile: str | bytes | os.PathLike[str] | os.PathLike[bytes] | None = None, + capath: str | bytes | os.PathLike[str] | os.PathLike[bytes] | None = None, + cadata: typing.Union[str, "Buffer", None] = None, + ) -> None: + return self._ctx.load_verify_locations( + cafile=cafile, capath=capath, cadata=cadata + ) + + def load_cert_chain( + self, + certfile: _StrOrBytesPath, + keyfile: _StrOrBytesPath | None = None, + password: _PasswordType | None = None, + ) -> None: + return self._ctx.load_cert_chain( + certfile=certfile, keyfile=keyfile, password=password + ) + + def load_default_certs( + self, purpose: ssl.Purpose = ssl.Purpose.SERVER_AUTH + ) -> None: + return self._ctx.load_default_certs(purpose) + + def set_alpn_protocols(self, alpn_protocols: typing.Iterable[str]) -> None: + return self._ctx.set_alpn_protocols(alpn_protocols) + + def set_npn_protocols(self, npn_protocols: typing.Iterable[str]) -> None: + return self._ctx.set_npn_protocols(npn_protocols) + + def set_ciphers(self, __cipherlist: str) -> None: + return self._ctx.set_ciphers(__cipherlist) + + def get_ciphers(self) -> typing.Any: + return self._ctx.get_ciphers() + + def session_stats(self) -> dict[str, int]: + return self._ctx.session_stats() + + def cert_store_stats(self) -> dict[str, int]: + raise NotImplementedError() + + def set_default_verify_paths(self) -> None: + self._ctx.set_default_verify_paths() + + @typing.overload + def get_ca_certs( + self, binary_form: typing.Literal[False] = ... + ) -> list[typing.Any]: ... + + @typing.overload + def get_ca_certs(self, binary_form: typing.Literal[True] = ...) -> list[bytes]: ... + + @typing.overload + def get_ca_certs(self, binary_form: bool = ...) -> typing.Any: ... + + def get_ca_certs(self, binary_form: bool = False) -> list[typing.Any] | list[bytes]: + raise NotImplementedError() + + @property + def check_hostname(self) -> bool: + return self._ctx.check_hostname + + @check_hostname.setter + def check_hostname(self, value: bool) -> None: + self._ctx.check_hostname = value + + @property + def hostname_checks_common_name(self) -> bool: + return self._ctx.hostname_checks_common_name + + @hostname_checks_common_name.setter + def hostname_checks_common_name(self, value: bool) -> None: + self._ctx.hostname_checks_common_name = value + + @property + def keylog_filename(self) -> str: + return self._ctx.keylog_filename + + @keylog_filename.setter + def keylog_filename(self, value: str) -> None: + self._ctx.keylog_filename = value + + @property + def maximum_version(self) -> ssl.TLSVersion: + return self._ctx.maximum_version + + @maximum_version.setter + def maximum_version(self, value: ssl.TLSVersion) -> None: + _original_super_SSLContext.maximum_version.__set__( # type: ignore[attr-defined] + self._ctx, value + ) + + @property + def minimum_version(self) -> ssl.TLSVersion: + return self._ctx.minimum_version + + @minimum_version.setter + def minimum_version(self, value: ssl.TLSVersion) -> None: + _original_super_SSLContext.minimum_version.__set__( # type: ignore[attr-defined] + self._ctx, value + ) + + @property + def options(self) -> ssl.Options: + return self._ctx.options + + @options.setter + def options(self, value: ssl.Options) -> None: + _original_super_SSLContext.options.__set__( # type: ignore[attr-defined] + self._ctx, value + ) + + @property + def post_handshake_auth(self) -> bool: + return self._ctx.post_handshake_auth + + @post_handshake_auth.setter + def post_handshake_auth(self, value: bool) -> None: + self._ctx.post_handshake_auth = value + + @property + def protocol(self) -> ssl._SSLMethod: + return self._ctx.protocol + + @property + def security_level(self) -> int: + return self._ctx.security_level + + @property + def verify_flags(self) -> ssl.VerifyFlags: + return self._ctx.verify_flags + + @verify_flags.setter + def verify_flags(self, value: ssl.VerifyFlags) -> None: + _original_super_SSLContext.verify_flags.__set__( # type: ignore[attr-defined] + self._ctx, value + ) + + @property + def verify_mode(self) -> ssl.VerifyMode: + return self._ctx.verify_mode + + @verify_mode.setter + def verify_mode(self, value: ssl.VerifyMode) -> None: + _original_super_SSLContext.verify_mode.__set__( # type: ignore[attr-defined] + self._ctx, value + ) + + +# Python 3.13+ makes get_unverified_chain() a public API that only returns DER +# encoded certificates. We detect whether we need to call public_bytes() for 3.10->3.12 +# Pre-3.13 returned None instead of an empty list from get_unverified_chain() +if sys.version_info >= (3, 13): + + def _get_unverified_chain_bytes(sslobj: ssl.SSLObject) -> list[bytes]: + unverified_chain = sslobj.get_unverified_chain() or () + return [ + cert if isinstance(cert, bytes) else cert.public_bytes(_ssl.ENCODING_DER) + for cert in unverified_chain + ] + +else: + + def _get_unverified_chain_bytes(sslobj: ssl.SSLObject) -> list[bytes]: + unverified_chain = sslobj.get_unverified_chain() or () # type: ignore[attr-defined] + return [cert.public_bytes(_ssl.ENCODING_DER) for cert in unverified_chain] + + +def _verify_peercerts( + sock_or_sslobj: ssl.SSLSocket | ssl.SSLObject, server_hostname: str | None +) -> None: + """ + Verifies the peer certificates from an SSLSocket or SSLObject + against the certificates in the OS trust store. + """ + sslobj: ssl.SSLObject = sock_or_sslobj # type: ignore[assignment] + try: + while not hasattr(sslobj, "get_unverified_chain"): + sslobj = sslobj._sslobj # type: ignore[attr-defined] + except AttributeError: + pass + + cert_bytes = _get_unverified_chain_bytes(sslobj) + _verify_peercerts_impl( + sock_or_sslobj.context, cert_bytes, server_hostname=server_hostname + ) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_macos.py b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_macos.py new file mode 100644 index 0000000000000000000000000000000000000000..345030772441102eda1be73661469a5f726d668a --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_macos.py @@ -0,0 +1,571 @@ +import contextlib +import ctypes +import platform +import ssl +import typing +from ctypes import ( + CDLL, + POINTER, + c_bool, + c_char_p, + c_int32, + c_long, + c_uint32, + c_ulong, + c_void_p, +) +from ctypes.util import find_library + +from ._ssl_constants import _set_ssl_context_verify_mode + +_mac_version = platform.mac_ver()[0] +_mac_version_info = tuple(map(int, _mac_version.split("."))) +if _mac_version_info < (10, 8): + raise ImportError( + f"Only OS X 10.8 and newer are supported, not {_mac_version_info[0]}.{_mac_version_info[1]}" + ) + +_is_macos_version_10_14_or_later = _mac_version_info >= (10, 14) + + +def _load_cdll(name: str, macos10_16_path: str) -> CDLL: + """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. + path: str | None + if _mac_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 ImportError(f"The library {name} failed to load") from 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 +CFError = c_void_p +CFType = c_void_p +CFTypeID = c_ulong +CFTypeRef = POINTER(CFType) +CFAllocatorRef = c_void_p + +OSStatus = c_int32 + +CFErrorRef = POINTER(CFError) +CFDataRef = POINTER(CFData) +CFStringRef = POINTER(CFString) +CFArrayRef = POINTER(CFArray) +CFMutableArrayRef = POINTER(CFMutableArray) +CFArrayCallBacks = c_void_p +CFOptionFlags = c_uint32 + +SecCertificateRef = POINTER(c_void_p) +SecPolicyRef = POINTER(c_void_p) +SecTrustRef = POINTER(c_void_p) +SecTrustResultType = c_uint32 +SecTrustOptionFlags = c_uint32 + +try: + 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.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] + Security.SecTrustSetAnchorCertificates.restype = OSStatus + + Security.SecTrustSetAnchorCertificatesOnly.argtypes = [SecTrustRef, Boolean] + Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus + + Security.SecPolicyCreateRevocation.argtypes = [CFOptionFlags] + Security.SecPolicyCreateRevocation.restype = SecPolicyRef + + Security.SecPolicyCreateSSL.argtypes = [Boolean, CFStringRef] + Security.SecPolicyCreateSSL.restype = SecPolicyRef + + Security.SecTrustCreateWithCertificates.argtypes = [ + CFTypeRef, + CFTypeRef, + POINTER(SecTrustRef), + ] + Security.SecTrustCreateWithCertificates.restype = OSStatus + + Security.SecTrustGetTrustResult.argtypes = [ + SecTrustRef, + POINTER(SecTrustResultType), + ] + Security.SecTrustGetTrustResult.restype = OSStatus + + Security.SecTrustEvaluate.argtypes = [ + SecTrustRef, + POINTER(SecTrustResultType), + ] + Security.SecTrustEvaluate.restype = OSStatus + + Security.SecTrustRef = SecTrustRef # type: ignore[attr-defined] + Security.SecTrustResultType = SecTrustResultType # type: ignore[attr-defined] + Security.OSStatus = OSStatus # type: ignore[attr-defined] + + kSecRevocationUseAnyAvailableMethod = 3 + kSecRevocationRequirePositiveResponse = 8 + + 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.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.CFErrorGetCode.argtypes = [CFErrorRef] + CoreFoundation.CFErrorGetCode.restype = CFIndex + + CoreFoundation.CFErrorCopyDescription.argtypes = [CFErrorRef] + CoreFoundation.CFErrorCopyDescription.restype = CFStringRef + + CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( # type: ignore[attr-defined] + CoreFoundation, "kCFAllocatorDefault" + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( # type: ignore[attr-defined] + CoreFoundation, "kCFTypeArrayCallBacks" + ) + + CoreFoundation.CFTypeRef = CFTypeRef # type: ignore[attr-defined] + CoreFoundation.CFArrayRef = CFArrayRef # type: ignore[attr-defined] + CoreFoundation.CFStringRef = CFStringRef # type: ignore[attr-defined] + CoreFoundation.CFErrorRef = CFErrorRef # type: ignore[attr-defined] + +except AttributeError as e: + raise ImportError(f"Error initializing ctypes: {e}") from None + +# SecTrustEvaluateWithError is macOS 10.14+ +if _is_macos_version_10_14_or_later: + try: + Security.SecTrustEvaluateWithError.argtypes = [ + SecTrustRef, + POINTER(CFErrorRef), + ] + Security.SecTrustEvaluateWithError.restype = c_bool + except AttributeError as e: + raise ImportError(f"Error initializing ctypes: {e}") from None + + +def _handle_osstatus(result: OSStatus, _: typing.Any, args: typing.Any) -> typing.Any: + """ + Raises an error if the OSStatus value is non-zero. + """ + if int(result) == 0: + return args + + # Returns a CFString which we need to transform + # into a UTF-8 Python string. + error_message_cfstring = None + try: + error_message_cfstring = Security.SecCopyErrorMessageString(result, None) + + # First step is convert the CFString into a C string pointer. + # We try the fast no-copy way first. + error_message_cfstring_c_void_p = ctypes.cast( + error_message_cfstring, ctypes.POINTER(ctypes.c_void_p) + ) + message = CoreFoundation.CFStringGetCStringPtr( + error_message_cfstring_c_void_p, CFConst.kCFStringEncodingUTF8 + ) + + # Quoting the Apple dev docs: + # + # "A pointer to a C string or NULL if the internal + # storage of theString does not allow this to be + # returned efficiently." + # + # So we need to get our hands dirty. + if message is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + error_message_cfstring_c_void_p, + buffer, + 1024, + CFConst.kCFStringEncodingUTF8, + ) + if not result: + raise OSError("Error copying C string from CFStringRef") + message = buffer.value + + finally: + if error_message_cfstring is not None: + CoreFoundation.CFRelease(error_message_cfstring) + + # If no message can be found for this status we come + # up with a generic one that forwards the status code. + if message is None or message == "": + message = f"SecureTransport operation returned a non-zero OSStatus: {result}" + + raise ssl.SSLError(message) + + +Security.SecTrustCreateWithCertificates.errcheck = _handle_osstatus # type: ignore[assignment] +Security.SecTrustSetAnchorCertificates.errcheck = _handle_osstatus # type: ignore[assignment] +Security.SecTrustSetAnchorCertificatesOnly.errcheck = _handle_osstatus # type: ignore[assignment] +Security.SecTrustGetTrustResult.errcheck = _handle_osstatus # type: ignore[assignment] +Security.SecTrustEvaluate.errcheck = _handle_osstatus # type: ignore[assignment] + + +class CFConst: + """CoreFoundation constants""" + + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) + + errSecIncompleteCertRevocationCheck = -67635 + errSecHostNameMismatch = -67602 + errSecCertificateExpired = -67818 + errSecNotTrusted = -67843 + + +def _bytes_to_cf_data_ref(value: bytes) -> CFDataRef: # type: ignore[valid-type] + return CoreFoundation.CFDataCreate( # type: ignore[no-any-return] + CoreFoundation.kCFAllocatorDefault, value, len(value) + ) + + +def _bytes_to_cf_string(value: bytes) -> CFString: + """ + Given a Python binary data, create a CFString. + The string must be CFReleased by the caller. + """ + c_str = ctypes.c_char_p(value) + cf_str = CoreFoundation.CFStringCreateWithCString( + CoreFoundation.kCFAllocatorDefault, + c_str, + CFConst.kCFStringEncodingUTF8, + ) + return cf_str # type: ignore[no-any-return] + + +def _cf_string_ref_to_str(cf_string_ref: CFStringRef) -> str | None: # type: ignore[valid-type] + """ + 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. + """ + + string = CoreFoundation.CFStringGetCStringPtr( + cf_string_ref, CFConst.kCFStringEncodingUTF8 + ) + if string is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + cf_string_ref, 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 # type: ignore[no-any-return] + + +def _der_certs_to_cf_cert_array(certs: list[bytes]) -> CFMutableArrayRef: # type: ignore[valid-type] + """Builds a CFArray of SecCertificateRefs from a list of DER-encoded certificates. + Responsibility of the caller to call CoreFoundation.CFRelease on the CFArray. + """ + cf_array = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cf_array: + raise MemoryError("Unable to allocate memory!") + + for cert_data in certs: + cf_data = None + sec_cert_ref = None + try: + cf_data = _bytes_to_cf_data_ref(cert_data) + sec_cert_ref = Security.SecCertificateCreateWithData( + CoreFoundation.kCFAllocatorDefault, cf_data + ) + CoreFoundation.CFArrayAppendValue(cf_array, sec_cert_ref) + finally: + if cf_data: + CoreFoundation.CFRelease(cf_data) + if sec_cert_ref: + CoreFoundation.CFRelease(sec_cert_ref) + + return cf_array # type: ignore[no-any-return] + + +@contextlib.contextmanager +def _configure_context(ctx: ssl.SSLContext) -> typing.Iterator[None]: + check_hostname = ctx.check_hostname + verify_mode = ctx.verify_mode + ctx.check_hostname = False + _set_ssl_context_verify_mode(ctx, ssl.CERT_NONE) + try: + yield + finally: + ctx.check_hostname = check_hostname + _set_ssl_context_verify_mode(ctx, verify_mode) + + +def _verify_peercerts_impl( + ssl_context: ssl.SSLContext, + cert_chain: list[bytes], + server_hostname: str | None = None, +) -> None: + certs = None + policies = None + trust = None + try: + # Only set a hostname on the policy if we're verifying the hostname + # on the leaf certificate. + if server_hostname is not None and ssl_context.check_hostname: + cf_str_hostname = None + try: + cf_str_hostname = _bytes_to_cf_string(server_hostname.encode("ascii")) + ssl_policy = Security.SecPolicyCreateSSL(True, cf_str_hostname) + finally: + if cf_str_hostname: + CoreFoundation.CFRelease(cf_str_hostname) + else: + ssl_policy = Security.SecPolicyCreateSSL(True, None) + + policies = ssl_policy + if ssl_context.verify_flags & ssl.VERIFY_CRL_CHECK_CHAIN: + # Add explicit policy requiring positive revocation checks + policies = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + CoreFoundation.CFArrayAppendValue(policies, ssl_policy) + CoreFoundation.CFRelease(ssl_policy) + revocation_policy = Security.SecPolicyCreateRevocation( + kSecRevocationUseAnyAvailableMethod + | kSecRevocationRequirePositiveResponse + ) + CoreFoundation.CFArrayAppendValue(policies, revocation_policy) + CoreFoundation.CFRelease(revocation_policy) + elif ssl_context.verify_flags & ssl.VERIFY_CRL_CHECK_LEAF: + raise NotImplementedError("VERIFY_CRL_CHECK_LEAF not implemented for macOS") + + certs = None + try: + certs = _der_certs_to_cf_cert_array(cert_chain) + + # Now that we have certificates loaded and a SecPolicy + # we can finally create a SecTrust object! + trust = Security.SecTrustRef() + Security.SecTrustCreateWithCertificates( + certs, policies, ctypes.byref(trust) + ) + + finally: + # The certs are now being held by SecTrust so we can + # release our handles for the array. + if certs: + CoreFoundation.CFRelease(certs) + + # If there are additional trust anchors to load we need to transform + # the list of DER-encoded certificates into a CFArray. + ctx_ca_certs_der: list[bytes] | None = ssl_context.get_ca_certs( + binary_form=True + ) + if ctx_ca_certs_der: + ctx_ca_certs = None + try: + ctx_ca_certs = _der_certs_to_cf_cert_array(ctx_ca_certs_der) + Security.SecTrustSetAnchorCertificates(trust, ctx_ca_certs) + finally: + if ctx_ca_certs: + CoreFoundation.CFRelease(ctx_ca_certs) + + # We always want system certificates. + Security.SecTrustSetAnchorCertificatesOnly(trust, False) + + # macOS 10.13 and earlier don't support SecTrustEvaluateWithError() + # so we use SecTrustEvaluate() which means we need to construct error + # messages ourselves. + if _is_macos_version_10_14_or_later: + _verify_peercerts_impl_macos_10_14(ssl_context, trust) + else: + _verify_peercerts_impl_macos_10_13(ssl_context, trust) + finally: + if policies: + CoreFoundation.CFRelease(policies) + if trust: + CoreFoundation.CFRelease(trust) + + +def _verify_peercerts_impl_macos_10_13( + ssl_context: ssl.SSLContext, sec_trust_ref: typing.Any +) -> None: + """Verify using 'SecTrustEvaluate' API for macOS 10.13 and earlier. + macOS 10.14 added the 'SecTrustEvaluateWithError' API. + """ + sec_trust_result_type = Security.SecTrustResultType() + Security.SecTrustEvaluate(sec_trust_ref, ctypes.byref(sec_trust_result_type)) + + try: + sec_trust_result_type_as_int = int(sec_trust_result_type.value) + except (ValueError, TypeError): + sec_trust_result_type_as_int = -1 + + # Apple doesn't document these values in their own API docs. + # See: https://github.com/xybp888/iOS-SDKs/blob/master/iPhoneOS13.0.sdk/System/Library/Frameworks/Security.framework/Headers/SecTrust.h#L84 + if ( + ssl_context.verify_mode == ssl.CERT_REQUIRED + and sec_trust_result_type_as_int not in (1, 4) + ): + # Note that we're not able to ignore only hostname errors + # for macOS 10.13 and earlier, so check_hostname=False will + # still return an error. + sec_trust_result_type_to_message = { + 0: "Invalid trust result type", + # 1: "Trust evaluation succeeded", + 2: "User confirmation required", + 3: "User specified that certificate is not trusted", + # 4: "Trust result is unspecified", + 5: "Recoverable trust failure occurred", + 6: "Fatal trust failure occurred", + 7: "Other error occurred, certificate may be revoked", + } + error_message = sec_trust_result_type_to_message.get( + sec_trust_result_type_as_int, + f"Unknown trust result: {sec_trust_result_type_as_int}", + ) + + err = ssl.SSLCertVerificationError(error_message) + err.verify_message = error_message + err.verify_code = sec_trust_result_type_as_int + raise err + + +def _verify_peercerts_impl_macos_10_14( + ssl_context: ssl.SSLContext, sec_trust_ref: typing.Any +) -> None: + """Verify using 'SecTrustEvaluateWithError' API for macOS 10.14+.""" + cf_error = CoreFoundation.CFErrorRef() + sec_trust_eval_result = Security.SecTrustEvaluateWithError( + sec_trust_ref, ctypes.byref(cf_error) + ) + # sec_trust_eval_result is a bool (0 or 1) + # where 1 means that the certs are trusted. + if sec_trust_eval_result == 1: + is_trusted = True + elif sec_trust_eval_result == 0: + is_trusted = False + else: + raise ssl.SSLError( + f"Unknown result from Security.SecTrustEvaluateWithError: {sec_trust_eval_result!r}" + ) + + cf_error_code = 0 + if not is_trusted: + cf_error_code = CoreFoundation.CFErrorGetCode(cf_error) + + # If the error is a known failure that we're + # explicitly okay with from SSLContext configuration + # we can set is_trusted accordingly. + if ssl_context.verify_mode != ssl.CERT_REQUIRED and ( + cf_error_code == CFConst.errSecNotTrusted + or cf_error_code == CFConst.errSecCertificateExpired + ): + is_trusted = True + + # If we're still not trusted then we start to + # construct and raise the SSLCertVerificationError. + if not is_trusted: + cf_error_string_ref = None + try: + cf_error_string_ref = CoreFoundation.CFErrorCopyDescription(cf_error) + + # Can this ever return 'None' if there's a CFError? + cf_error_message = ( + _cf_string_ref_to_str(cf_error_string_ref) + or "Certificate verification failed" + ) + + # TODO: Not sure if we need the SecTrustResultType for anything? + # We only care whether or not it's a success or failure for now. + sec_trust_result_type = Security.SecTrustResultType() + Security.SecTrustGetTrustResult( + sec_trust_ref, ctypes.byref(sec_trust_result_type) + ) + + err = ssl.SSLCertVerificationError(cf_error_message) + err.verify_message = cf_error_message + err.verify_code = cf_error_code + raise err + finally: + if cf_error_string_ref: + CoreFoundation.CFRelease(cf_error_string_ref) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_openssl.py b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_openssl.py new file mode 100644 index 0000000000000000000000000000000000000000..3e25a56773aa5d796581eb41415dda5087eb1460 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_openssl.py @@ -0,0 +1,68 @@ +import contextlib +import os +import re +import ssl +import typing + +# candidates based on https://github.com/tiran/certifi-system-store by Christian Heimes +_CA_FILE_CANDIDATES = [ + # Alpine, Arch, Fedora 34-42, OpenWRT, RHEL 9-10, BSD + "/etc/ssl/cert.pem", + # Fedora 43+, RHEL 11+ + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", + # Fedora <= 34, RHEL <= 9, CentOS <= 9 + "/etc/pki/tls/cert.pem", + # Debian, Ubuntu (requires ca-certificates) + "/etc/ssl/certs/ca-certificates.crt", + # SUSE + "/etc/ssl/ca-bundle.pem", +] + +_HASHED_CERT_FILENAME_RE = re.compile(r"^[0-9a-fA-F]{8}\.[0-9]$") + + +@contextlib.contextmanager +def _configure_context(ctx: ssl.SSLContext) -> typing.Iterator[None]: + # First, check whether the default locations from OpenSSL + # seem like they will give us a usable set of CA certs. + # ssl.get_default_verify_paths already takes care of: + # - getting cafile from either the SSL_CERT_FILE env var + # or the path configured when OpenSSL was compiled, + # and verifying that that path exists + # - getting capath from either the SSL_CERT_DIR env var + # or the path configured when OpenSSL was compiled, + # and verifying that that path exists + # In addition we'll check whether capath appears to contain certs. + defaults = ssl.get_default_verify_paths() + if defaults.cafile or (defaults.capath and _capath_contains_certs(defaults.capath)): + ctx.set_default_verify_paths() + else: + # cafile from OpenSSL doesn't exist + # and capath from OpenSSL doesn't contain certs. + # Let's search other common locations instead. + for cafile in _CA_FILE_CANDIDATES: + if os.path.isfile(cafile): + ctx.load_verify_locations(cafile=cafile) + break + + yield + + +def _capath_contains_certs(capath: str) -> bool: + """Check whether capath exists and contains certs in the expected format.""" + if not os.path.isdir(capath): + return False + for name in os.listdir(capath): + if _HASHED_CERT_FILENAME_RE.match(name): + return True + return False + + +def _verify_peercerts_impl( + ssl_context: ssl.SSLContext, + cert_chain: list[bytes], + server_hostname: str | None = None, +) -> None: + # This is a no-op because we've enabled SSLContext's built-in + # verification via verify_mode=CERT_REQUIRED, and don't need to repeat it. + pass diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_ssl_constants.py b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_ssl_constants.py new file mode 100644 index 0000000000000000000000000000000000000000..b1ee7a3cb13a8a3482f2cb8cadef097087e3780b --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_ssl_constants.py @@ -0,0 +1,31 @@ +import ssl +import sys +import typing + +# Hold on to the original class so we can create it consistently +# even if we inject our own SSLContext into the ssl module. +_original_SSLContext = ssl.SSLContext +_original_super_SSLContext = super(_original_SSLContext, _original_SSLContext) + +# CPython is known to be good, but non-CPython implementations +# may implement SSLContext differently so to be safe we don't +# subclass the SSLContext. + +# This is returned by truststore.SSLContext.__class__() +_truststore_SSLContext_dunder_class: typing.Optional[type] + +# This value is the superclass of truststore.SSLContext. +_truststore_SSLContext_super_class: type + +if sys.implementation.name == "cpython": + _truststore_SSLContext_super_class = _original_SSLContext + _truststore_SSLContext_dunder_class = None +else: + _truststore_SSLContext_super_class = object + _truststore_SSLContext_dunder_class = _original_SSLContext + + +def _set_ssl_context_verify_mode( + ssl_context: ssl.SSLContext, verify_mode: ssl.VerifyMode +) -> None: + _original_super_SSLContext.verify_mode.__set__(ssl_context, verify_mode) # type: ignore[attr-defined] diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_windows.py b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_windows.py new file mode 100644 index 0000000000000000000000000000000000000000..a9bf9abdfc8ed065a2253af17daa07cce4009022 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/_windows.py @@ -0,0 +1,567 @@ +import contextlib +import ssl +import typing +from ctypes import WinDLL # type: ignore +from ctypes import WinError # type: ignore +from ctypes import ( + POINTER, + Structure, + c_char_p, + c_ulong, + c_void_p, + c_wchar_p, + cast, + create_unicode_buffer, + pointer, + sizeof, +) +from ctypes.wintypes import ( + BOOL, + DWORD, + HANDLE, + LONG, + LPCSTR, + LPCVOID, + LPCWSTR, + LPFILETIME, + LPSTR, + LPWSTR, +) +from typing import TYPE_CHECKING, Any + +from ._ssl_constants import _set_ssl_context_verify_mode + +HCERTCHAINENGINE = HANDLE +HCERTSTORE = HANDLE +HCRYPTPROV_LEGACY = HANDLE + + +class CERT_CONTEXT(Structure): + _fields_ = ( + ("dwCertEncodingType", DWORD), + ("pbCertEncoded", c_void_p), + ("cbCertEncoded", DWORD), + ("pCertInfo", c_void_p), + ("hCertStore", HCERTSTORE), + ) + + +PCERT_CONTEXT = POINTER(CERT_CONTEXT) +PCCERT_CONTEXT = POINTER(PCERT_CONTEXT) + + +class CERT_ENHKEY_USAGE(Structure): + _fields_ = ( + ("cUsageIdentifier", DWORD), + ("rgpszUsageIdentifier", POINTER(LPSTR)), + ) + + +PCERT_ENHKEY_USAGE = POINTER(CERT_ENHKEY_USAGE) + + +class CERT_USAGE_MATCH(Structure): + _fields_ = ( + ("dwType", DWORD), + ("Usage", CERT_ENHKEY_USAGE), + ) + + +class CERT_CHAIN_PARA(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("RequestedUsage", CERT_USAGE_MATCH), + ("RequestedIssuancePolicy", CERT_USAGE_MATCH), + ("dwUrlRetrievalTimeout", DWORD), + ("fCheckRevocationFreshnessTime", BOOL), + ("dwRevocationFreshnessTime", DWORD), + ("pftCacheResync", LPFILETIME), + ("pStrongSignPara", c_void_p), + ("dwStrongSignFlags", DWORD), + ) + + +if TYPE_CHECKING: + PCERT_CHAIN_PARA = pointer[CERT_CHAIN_PARA] # type: ignore[misc] +else: + PCERT_CHAIN_PARA = POINTER(CERT_CHAIN_PARA) + + +class CERT_TRUST_STATUS(Structure): + _fields_ = ( + ("dwErrorStatus", DWORD), + ("dwInfoStatus", DWORD), + ) + + +class CERT_CHAIN_ELEMENT(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("pCertContext", PCERT_CONTEXT), + ("TrustStatus", CERT_TRUST_STATUS), + ("pRevocationInfo", c_void_p), + ("pIssuanceUsage", PCERT_ENHKEY_USAGE), + ("pApplicationUsage", PCERT_ENHKEY_USAGE), + ("pwszExtendedErrorInfo", LPCWSTR), + ) + + +PCERT_CHAIN_ELEMENT = POINTER(CERT_CHAIN_ELEMENT) + + +class CERT_SIMPLE_CHAIN(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("TrustStatus", CERT_TRUST_STATUS), + ("cElement", DWORD), + ("rgpElement", POINTER(PCERT_CHAIN_ELEMENT)), + ("pTrustListInfo", c_void_p), + ("fHasRevocationFreshnessTime", BOOL), + ("dwRevocationFreshnessTime", DWORD), + ) + + +PCERT_SIMPLE_CHAIN = POINTER(CERT_SIMPLE_CHAIN) + + +class CERT_CHAIN_CONTEXT(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("TrustStatus", CERT_TRUST_STATUS), + ("cChain", DWORD), + ("rgpChain", POINTER(PCERT_SIMPLE_CHAIN)), + ("cLowerQualityChainContext", DWORD), + ("rgpLowerQualityChainContext", c_void_p), + ("fHasRevocationFreshnessTime", BOOL), + ("dwRevocationFreshnessTime", DWORD), + ) + + +PCERT_CHAIN_CONTEXT = POINTER(CERT_CHAIN_CONTEXT) +PCCERT_CHAIN_CONTEXT = POINTER(PCERT_CHAIN_CONTEXT) + + +class SSL_EXTRA_CERT_CHAIN_POLICY_PARA(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("dwAuthType", DWORD), + ("fdwChecks", DWORD), + ("pwszServerName", LPCWSTR), + ) + + +class CERT_CHAIN_POLICY_PARA(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("dwFlags", DWORD), + ("pvExtraPolicyPara", c_void_p), + ) + + +PCERT_CHAIN_POLICY_PARA = POINTER(CERT_CHAIN_POLICY_PARA) + + +class CERT_CHAIN_POLICY_STATUS(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("dwError", DWORD), + ("lChainIndex", LONG), + ("lElementIndex", LONG), + ("pvExtraPolicyStatus", c_void_p), + ) + + +PCERT_CHAIN_POLICY_STATUS = POINTER(CERT_CHAIN_POLICY_STATUS) + + +class CERT_CHAIN_ENGINE_CONFIG(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("hRestrictedRoot", HCERTSTORE), + ("hRestrictedTrust", HCERTSTORE), + ("hRestrictedOther", HCERTSTORE), + ("cAdditionalStore", DWORD), + ("rghAdditionalStore", c_void_p), + ("dwFlags", DWORD), + ("dwUrlRetrievalTimeout", DWORD), + ("MaximumCachedCertificates", DWORD), + ("CycleDetectionModulus", DWORD), + ("hExclusiveRoot", HCERTSTORE), + ("hExclusiveTrustedPeople", HCERTSTORE), + ("dwExclusiveFlags", DWORD), + ) + + +PCERT_CHAIN_ENGINE_CONFIG = POINTER(CERT_CHAIN_ENGINE_CONFIG) +PHCERTCHAINENGINE = POINTER(HCERTCHAINENGINE) + +X509_ASN_ENCODING = 0x00000001 +PKCS_7_ASN_ENCODING = 0x00010000 +CERT_STORE_PROV_MEMORY = b"Memory" +CERT_STORE_ADD_USE_EXISTING = 2 +USAGE_MATCH_TYPE_OR = 1 +OID_PKIX_KP_SERVER_AUTH = c_char_p(b"1.3.6.1.5.5.7.3.1") +CERT_CHAIN_REVOCATION_CHECK_END_CERT = 0x10000000 +CERT_CHAIN_REVOCATION_CHECK_CHAIN = 0x20000000 +CERT_CHAIN_POLICY_IGNORE_ALL_NOT_TIME_VALID_FLAGS = 0x00000007 +CERT_CHAIN_POLICY_IGNORE_INVALID_BASIC_CONSTRAINTS_FLAG = 0x00000008 +CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG = 0x00000010 +CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG = 0x00000040 +CERT_CHAIN_POLICY_IGNORE_WRONG_USAGE_FLAG = 0x00000020 +CERT_CHAIN_POLICY_IGNORE_INVALID_POLICY_FLAG = 0x00000080 +CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS = 0x00000F00 +CERT_CHAIN_POLICY_ALLOW_TESTROOT_FLAG = 0x00008000 +CERT_CHAIN_POLICY_TRUST_TESTROOT_FLAG = 0x00004000 +SECURITY_FLAG_IGNORE_CERT_CN_INVALID = 0x00001000 +AUTHTYPE_SERVER = 2 +CERT_CHAIN_POLICY_SSL = 4 +FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 +FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 + +# Flags to set for SSLContext.verify_mode=CERT_NONE +CERT_CHAIN_POLICY_VERIFY_MODE_NONE_FLAGS = ( + CERT_CHAIN_POLICY_IGNORE_ALL_NOT_TIME_VALID_FLAGS + | CERT_CHAIN_POLICY_IGNORE_INVALID_BASIC_CONSTRAINTS_FLAG + | CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG + | CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG + | CERT_CHAIN_POLICY_IGNORE_WRONG_USAGE_FLAG + | CERT_CHAIN_POLICY_IGNORE_INVALID_POLICY_FLAG + | CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS + | CERT_CHAIN_POLICY_ALLOW_TESTROOT_FLAG + | CERT_CHAIN_POLICY_TRUST_TESTROOT_FLAG +) + +wincrypt = WinDLL("crypt32.dll") +kernel32 = WinDLL("kernel32.dll") + + +def _handle_win_error(result: bool, _: Any, args: Any) -> Any: + if not result: + # Note, actually raises OSError after calling GetLastError and FormatMessage + raise WinError() + return args + + +CertCreateCertificateChainEngine = wincrypt.CertCreateCertificateChainEngine +CertCreateCertificateChainEngine.argtypes = ( + PCERT_CHAIN_ENGINE_CONFIG, + PHCERTCHAINENGINE, +) +CertCreateCertificateChainEngine.errcheck = _handle_win_error + +CertOpenStore = wincrypt.CertOpenStore +CertOpenStore.argtypes = (LPCSTR, DWORD, HCRYPTPROV_LEGACY, DWORD, c_void_p) +CertOpenStore.restype = HCERTSTORE +CertOpenStore.errcheck = _handle_win_error + +CertAddEncodedCertificateToStore = wincrypt.CertAddEncodedCertificateToStore +CertAddEncodedCertificateToStore.argtypes = ( + HCERTSTORE, + DWORD, + c_char_p, + DWORD, + DWORD, + PCCERT_CONTEXT, +) +CertAddEncodedCertificateToStore.restype = BOOL + +CertCreateCertificateContext = wincrypt.CertCreateCertificateContext +CertCreateCertificateContext.argtypes = (DWORD, c_char_p, DWORD) +CertCreateCertificateContext.restype = PCERT_CONTEXT +CertCreateCertificateContext.errcheck = _handle_win_error + +CertGetCertificateChain = wincrypt.CertGetCertificateChain +CertGetCertificateChain.argtypes = ( + HCERTCHAINENGINE, + PCERT_CONTEXT, + LPFILETIME, + HCERTSTORE, + PCERT_CHAIN_PARA, + DWORD, + c_void_p, + PCCERT_CHAIN_CONTEXT, +) +CertGetCertificateChain.restype = BOOL +CertGetCertificateChain.errcheck = _handle_win_error + +CertVerifyCertificateChainPolicy = wincrypt.CertVerifyCertificateChainPolicy +CertVerifyCertificateChainPolicy.argtypes = ( + c_ulong, + PCERT_CHAIN_CONTEXT, + PCERT_CHAIN_POLICY_PARA, + PCERT_CHAIN_POLICY_STATUS, +) +CertVerifyCertificateChainPolicy.restype = BOOL + +CertCloseStore = wincrypt.CertCloseStore +CertCloseStore.argtypes = (HCERTSTORE, DWORD) +CertCloseStore.restype = BOOL +CertCloseStore.errcheck = _handle_win_error + +CertFreeCertificateChain = wincrypt.CertFreeCertificateChain +CertFreeCertificateChain.argtypes = (PCERT_CHAIN_CONTEXT,) + +CertFreeCertificateContext = wincrypt.CertFreeCertificateContext +CertFreeCertificateContext.argtypes = (PCERT_CONTEXT,) + +CertFreeCertificateChainEngine = wincrypt.CertFreeCertificateChainEngine +CertFreeCertificateChainEngine.argtypes = (HCERTCHAINENGINE,) + +FormatMessageW = kernel32.FormatMessageW +FormatMessageW.argtypes = ( + DWORD, + LPCVOID, + DWORD, + DWORD, + LPWSTR, + DWORD, + c_void_p, +) +FormatMessageW.restype = DWORD + + +def _verify_peercerts_impl( + ssl_context: ssl.SSLContext, + cert_chain: list[bytes], + server_hostname: str | None = None, +) -> None: + """Verify the cert_chain from the server using Windows APIs.""" + + # If the peer didn't send any certificates then + # we can't do verification. Raise an error. + if not cert_chain: + raise ssl.SSLCertVerificationError("Peer sent no certificates to verify") + + pCertContext = None + hIntermediateCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, None, 0, None) + try: + # Add intermediate certs to an in-memory cert store + for cert_bytes in cert_chain[1:]: + CertAddEncodedCertificateToStore( + hIntermediateCertStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + cert_bytes, + len(cert_bytes), + CERT_STORE_ADD_USE_EXISTING, + None, + ) + + # Cert context for leaf cert + leaf_cert = cert_chain[0] + pCertContext = CertCreateCertificateContext( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, leaf_cert, len(leaf_cert) + ) + + # Chain params to match certs for serverAuth extended usage + cert_enhkey_usage = CERT_ENHKEY_USAGE() + cert_enhkey_usage.cUsageIdentifier = 1 + cert_enhkey_usage.rgpszUsageIdentifier = (c_char_p * 1)(OID_PKIX_KP_SERVER_AUTH) + cert_usage_match = CERT_USAGE_MATCH() + cert_usage_match.Usage = cert_enhkey_usage + chain_params = CERT_CHAIN_PARA() + chain_params.RequestedUsage = cert_usage_match + chain_params.cbSize = sizeof(chain_params) + pChainPara = pointer(chain_params) + + if ssl_context.verify_flags & ssl.VERIFY_CRL_CHECK_CHAIN: + chain_flags = CERT_CHAIN_REVOCATION_CHECK_CHAIN + elif ssl_context.verify_flags & ssl.VERIFY_CRL_CHECK_LEAF: + chain_flags = CERT_CHAIN_REVOCATION_CHECK_END_CERT + else: + chain_flags = 0 + + try: + # First attempt to verify using the default Windows system trust roots + # (default chain engine). + _get_and_verify_cert_chain( + ssl_context, + None, + hIntermediateCertStore, + pCertContext, + pChainPara, + server_hostname, + chain_flags=chain_flags, + ) + except ssl.SSLCertVerificationError as e: + # If that fails but custom CA certs have been added + # to the SSLContext using load_verify_locations, + # try verifying using a custom chain engine + # that trusts the custom CA certs. + custom_ca_certs: list[bytes] | None = ssl_context.get_ca_certs( + binary_form=True + ) + if custom_ca_certs: + try: + _verify_using_custom_ca_certs( + ssl_context, + custom_ca_certs, + hIntermediateCertStore, + pCertContext, + pChainPara, + server_hostname, + chain_flags=chain_flags, + ) + # Raise the original error, not the new error. + except ssl.SSLCertVerificationError: + raise e from None + else: + raise + finally: + CertCloseStore(hIntermediateCertStore, 0) + if pCertContext: + CertFreeCertificateContext(pCertContext) + + +def _get_and_verify_cert_chain( + ssl_context: ssl.SSLContext, + hChainEngine: HCERTCHAINENGINE | None, + hIntermediateCertStore: HCERTSTORE, + pPeerCertContext: c_void_p, + pChainPara: PCERT_CHAIN_PARA, # type: ignore[valid-type] + server_hostname: str | None, + chain_flags: int, +) -> None: + ppChainContext = None + try: + # Get cert chain + ppChainContext = pointer(PCERT_CHAIN_CONTEXT()) + CertGetCertificateChain( + hChainEngine, # chain engine + pPeerCertContext, # leaf cert context + None, # current system time + hIntermediateCertStore, # additional in-memory cert store + pChainPara, # chain-building parameters + chain_flags, + None, # reserved + ppChainContext, # the resulting chain context + ) + pChainContext = ppChainContext.contents + + # Verify cert chain + ssl_extra_cert_chain_policy_para = SSL_EXTRA_CERT_CHAIN_POLICY_PARA() + ssl_extra_cert_chain_policy_para.cbSize = sizeof( + ssl_extra_cert_chain_policy_para + ) + ssl_extra_cert_chain_policy_para.dwAuthType = AUTHTYPE_SERVER + ssl_extra_cert_chain_policy_para.fdwChecks = 0 + if ssl_context.check_hostname is False: + ssl_extra_cert_chain_policy_para.fdwChecks = ( + SECURITY_FLAG_IGNORE_CERT_CN_INVALID + ) + if server_hostname: + ssl_extra_cert_chain_policy_para.pwszServerName = c_wchar_p(server_hostname) + + chain_policy = CERT_CHAIN_POLICY_PARA() + chain_policy.pvExtraPolicyPara = cast( + pointer(ssl_extra_cert_chain_policy_para), c_void_p + ) + if ssl_context.verify_mode == ssl.CERT_NONE: + chain_policy.dwFlags |= CERT_CHAIN_POLICY_VERIFY_MODE_NONE_FLAGS + chain_policy.cbSize = sizeof(chain_policy) + + pPolicyPara = pointer(chain_policy) + policy_status = CERT_CHAIN_POLICY_STATUS() + policy_status.cbSize = sizeof(policy_status) + pPolicyStatus = pointer(policy_status) + CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + pChainContext, + pPolicyPara, + pPolicyStatus, + ) + + # Check status + error_code = policy_status.dwError + if error_code: + # Try getting a human readable message for an error code. + error_message_buf = create_unicode_buffer(1024) + error_message_chars = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + None, + error_code, + 0, + error_message_buf, + sizeof(error_message_buf), + None, + ) + + # See if we received a message for the error, + # otherwise we use a generic error with the + # error code and hope that it's search-able. + if error_message_chars <= 0: + error_message = f"Certificate chain policy error {error_code:#x} [{policy_status.lElementIndex}]" + else: + error_message = error_message_buf.value.strip() + + err = ssl.SSLCertVerificationError(error_message) + err.verify_message = error_message + err.verify_code = error_code + raise err from None + finally: + if ppChainContext: + CertFreeCertificateChain(ppChainContext.contents) + + +def _verify_using_custom_ca_certs( + ssl_context: ssl.SSLContext, + custom_ca_certs: list[bytes], + hIntermediateCertStore: HCERTSTORE, + pPeerCertContext: c_void_p, + pChainPara: PCERT_CHAIN_PARA, # type: ignore[valid-type] + server_hostname: str | None, + chain_flags: int, +) -> None: + hChainEngine = None + hRootCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, None, 0, None) + try: + # Add custom CA certs to an in-memory cert store + for cert_bytes in custom_ca_certs: + CertAddEncodedCertificateToStore( + hRootCertStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + cert_bytes, + len(cert_bytes), + CERT_STORE_ADD_USE_EXISTING, + None, + ) + + # Create a custom cert chain engine which exclusively trusts + # certs from our hRootCertStore + cert_chain_engine_config = CERT_CHAIN_ENGINE_CONFIG() + cert_chain_engine_config.cbSize = sizeof(cert_chain_engine_config) + cert_chain_engine_config.hExclusiveRoot = hRootCertStore + pConfig = pointer(cert_chain_engine_config) + phChainEngine = pointer(HCERTCHAINENGINE()) + CertCreateCertificateChainEngine( + pConfig, + phChainEngine, + ) + hChainEngine = phChainEngine.contents + + # Get and verify a cert chain using the custom chain engine + _get_and_verify_cert_chain( + ssl_context, + hChainEngine, + hIntermediateCertStore, + pPeerCertContext, + pChainPara, + server_hostname, + chain_flags, + ) + finally: + if hChainEngine: + CertFreeCertificateChainEngine(hChainEngine) + CertCloseStore(hRootCertStore, 0) + + +@contextlib.contextmanager +def _configure_context(ctx: ssl.SSLContext) -> typing.Iterator[None]: + check_hostname = ctx.check_hostname + verify_mode = ctx.verify_mode + ctx.check_hostname = False + _set_ssl_context_verify_mode(ctx, ssl.CERT_NONE) + try: + yield + finally: + ctx.check_hostname = check_hostname + _set_ssl_context_verify_mode(ctx, verify_mode) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/truststore/py.typed b/hug/lib/python3.12/site-packages/pip/_vendor/truststore/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/LICENSE.txt b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..e6183d0276b26c5b87aecccf8d0d5bcd7b1148d4 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2008-2020 Andrey Petrov and 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/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__init__.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3fe782c8a45bbabcf240f3cac4303ac12b0ec274 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__init__.py @@ -0,0 +1,211 @@ +""" +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more +""" + +from __future__ import annotations + +# Set default logging handler to avoid "No handler found" warnings. +import logging +import sys +import typing +import warnings +from logging import NullHandler + +from . import exceptions +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url +from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import BaseHTTPResponse, HTTPResponse +from .util.request import make_headers +from .util.retry import Retry +from .util.timeout import Timeout + +# Ensure that Python is compiled with OpenSSL 1.1.1+ +# If the 'ssl' module isn't available at all that's +# fine, we only care if the module is available. +try: + import ssl +except ImportError: + pass +else: + if not ssl.OPENSSL_VERSION.startswith("OpenSSL "): # Defensive: + warnings.warn( + "urllib3 v2 only supports OpenSSL 1.1.1+, currently " + f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " + "See: https://github.com/urllib3/urllib3/issues/3020", + exceptions.NotOpenSSLWarning, + ) + elif ssl.OPENSSL_VERSION_INFO < (1, 1, 1): # Defensive: + raise ImportError( + "urllib3 v2 only supports OpenSSL 1.1.1+, currently " + f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " + "See: https://github.com/urllib3/urllib3/issues/2168" + ) + +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ + +__all__ = ( + "HTTPConnectionPool", + "HTTPHeaderDict", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "make_headers", + "proxy_from_url", + "request", + "BaseHTTPResponse", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger( + level: int = logging.DEBUG, +) -> logging.StreamHandler[typing.TextIO]: + """ + 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) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) + + +def disable_warnings(category: type[Warning] = exceptions.HTTPWarning) -> None: + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter("ignore", category) + + +_DEFAULT_POOL = PoolManager() + + +def request( + method: str, + url: str, + *, + body: _TYPE_BODY | None = None, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + preload_content: bool | None = True, + decode_content: bool | None = True, + redirect: bool | None = True, + retries: Retry | bool | int | None = None, + timeout: Timeout | float | int | None = 3, + json: typing.Any | None = None, +) -> BaseHTTPResponse: + """ + A convenience, top-level request method. It uses a module-global ``PoolManager`` instance. + Therefore, its side effects could be shared across dependencies relying on it. + To avoid side effects create a new ``PoolManager`` instance and use it instead. + The method does not accept low-level ``**urlopen_kw`` keyword arguments. + + :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 fields: + Data to encode and send in the request body. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. + + :param bool preload_content: + If True, the response's body will be preloaded into memory. + + :param bool decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :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 retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. 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 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 json: + Data to encode and send as JSON with UTF-encoded in the request body. + The ``"Content-Type"`` header will be set to ``"application/json"`` + unless specified otherwise. + """ + + return _DEFAULT_POOL.request( + method, + url, + body=body, + fields=fields, + headers=headers, + preload_content=preload_content, + decode_content=decode_content, + redirect=redirect, + retries=retries, + timeout=timeout, + json=json, + ) + + +if sys.platform == "emscripten": + from .contrib.emscripten import inject_into_urllib3 # noqa: 401 + + inject_into_urllib3() diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2ec93db74abcd6f9c40c3319fe219f236440ba4 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_base_connection.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_base_connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c24e0ec606c05317cc8cc5acf03d91bbe92ae7ab Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_base_connection.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db2c8d4a8ceb943e9a79fa761ba967728ded9d4e Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_request_methods.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_request_methods.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..decfe64eb2a750de2a5ef73f301a172a161837dc Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_request_methods.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b559853584a0c2e1bff98b6fd416c4c03a712fc5 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbe765459ceb0e00176642087f7c0e7f430e05b6 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40dddd5abd4f26e605c3dfceac478e3de0d1a998 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c7a9df4e03aa09320bb91807d8d59e7e8b1451b Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40247458113c5b33b31c20a52b0fe2ca66ed5bac Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0a41ec9cf45815569d2f0330ee2c70c08bc48a2 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..deda490223f6240e35b600a9c2efd4adee1137c7 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30e40bafd8bbce9a97021f08e4a3490caac09ab5 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_base_connection.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_base_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..992ec1657a9d0a98354b56b2a55d54b75db0eaa3 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_base_connection.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import typing + +from .util.connection import _TYPE_SOCKET_OPTIONS +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from .util.url import Url + +_TYPE_BODY = typing.Union[ + bytes, typing.IO[typing.Any], typing.Iterable[bytes | str], str +] + + +class ProxyConfig(typing.NamedTuple): + ssl_context: ssl.SSLContext | None + use_forwarding_for_https: bool + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None + + +class _ResponseOptions(typing.NamedTuple): + # TODO: Remove this in favor of a better + # HTTP request/response lifecycle tracking. + request_method: str + request_url: str + preload_content: bool + decode_content: bool + enforce_content_length: bool + + +if typing.TYPE_CHECKING: + import ssl + from typing import Protocol + + from .response import BaseHTTPResponse + + class BaseHTTPConnection(Protocol): + default_port: typing.ClassVar[int] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + host: str + port: int + timeout: None | ( + float + ) # Instance doesn't store _DEFAULT_TIMEOUT, must be resolved. + blocksize: int + source_address: tuple[str, int] | None + socket_options: _TYPE_SOCKET_OPTIONS | None + + proxy: Url | None + proxy_config: ProxyConfig | None + + is_verified: bool + proxy_is_verified: bool | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + socket_options: _TYPE_SOCKET_OPTIONS | None = ..., + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: ... + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: ... + + def connect(self) -> None: ... + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + # We know *at least* botocore is depending on the order of the + # first 3 parameters so to be safe we only mark the later ones + # as keyword-only to ensure we have space to extend. + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: ... + + def getresponse(self) -> BaseHTTPResponse: ... + + def close(self) -> None: ... + + @property + def is_closed(self) -> bool: + """Whether the connection either is brand new or has been previously closed. + If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` + properties must be False. + """ + + @property + def is_connected(self) -> bool: + """Whether the connection is actively connected to any origin (proxy or target)""" + + @property + def has_connected_to_proxy(self) -> bool: + """Whether the connection has successfully connected to its proxy. + This returns False if no proxy is in use. Used to determine whether + errors are coming from the proxy layer or from tunnelling to the target origin. + """ + + class BaseHTTPSConnection(BaseHTTPConnection, Protocol): + default_port: typing.ClassVar[int] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + # Certificate verification methods + cert_reqs: int | str | None + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None + ssl_context: ssl.SSLContext | None + + # Trusted CAs + ca_certs: str | None + ca_cert_dir: str | None + ca_cert_data: None | str | bytes + + # TLS version + ssl_minimum_version: int | None + ssl_maximum_version: int | None + ssl_version: int | str | None # Deprecated + + # Client certificates + cert_file: str | None + key_file: str | None + key_password: str | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: _TYPE_SOCKET_OPTIONS | None = ..., + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: ... diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_collections.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_collections.py new file mode 100644 index 0000000000000000000000000000000000000000..ee9ca662b625ce6b0a4743d05a186301b9a30ee6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_collections.py @@ -0,0 +1,486 @@ +from __future__ import annotations + +import typing +from collections import OrderedDict +from enum import Enum, auto +from threading import RLock + +if typing.TYPE_CHECKING: + # We can only import Protocol if TYPE_CHECKING because it's a development + # dependency, and is not available at runtime. + from typing import Protocol + + from typing_extensions import Self + + class HasGettableStringKeys(Protocol): + def keys(self) -> typing.Iterator[str]: ... + + def __getitem__(self, key: str) -> str: ... + + +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] + + +# Key type +_KT = typing.TypeVar("_KT") +# Value type +_VT = typing.TypeVar("_VT") +# Default type +_DT = typing.TypeVar("_DT") + +ValidHTTPHeaderSource = typing.Union[ + "HTTPHeaderDict", + typing.Mapping[str, str], + typing.Iterable[tuple[str, str]], + "HasGettableStringKeys", +] + + +class _Sentinel(Enum): + not_passed = auto() + + +def ensure_can_construct_http_header_dict( + potential: object, +) -> ValidHTTPHeaderSource | None: + if isinstance(potential, HTTPHeaderDict): + return potential + elif isinstance(potential, typing.Mapping): + # Full runtime checking of the contents of a Mapping is expensive, so for the + # purposes of typechecking, we assume that any Mapping is the right shape. + return typing.cast(typing.Mapping[str, str], potential) + elif isinstance(potential, typing.Iterable): + # Similarly to Mapping, full runtime checking of the contents of an Iterable is + # expensive, so for the purposes of typechecking, we assume that any Iterable + # is the right shape. + return typing.cast(typing.Iterable[tuple[str, str]], potential) + elif hasattr(potential, "keys") and hasattr(potential, "__getitem__"): + return typing.cast("HasGettableStringKeys", potential) + else: + return None + + +class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, _VT]): + """ + 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 + """ + + _container: typing.OrderedDict[_KT, _VT] + _maxsize: int + dispose_func: typing.Callable[[_VT], None] | None + lock: RLock + + def __init__( + self, + maxsize: int = 10, + dispose_func: typing.Callable[[_VT], None] | None = None, + ) -> None: + super().__init__() + self._maxsize = maxsize + self.dispose_func = dispose_func + self._container = OrderedDict() + self.lock = RLock() + + def __getitem__(self, key: _KT) -> _VT: + # 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: _KT, value: _VT) -> None: + evicted_item = None + with self.lock: + # Possibly evict the existing value of 'key' + try: + # If the key exists, we'll overwrite it, which won't change the + # size of the pool. Because accessing a key should move it to + # the end of the eviction line, we pop it out first. + evicted_item = key, self._container.pop(key) + self._container[key] = value + except KeyError: + # When the key does not exist, we insert the value first so that + # evicting works in all cases, including when self._maxsize is 0 + self._container[key] = value + if len(self._container) > self._maxsize: + # If we didn't evict an existing value, and we've hit our maximum + # size, then we have to evict the least recently used item from + # the beginning of the container. + evicted_item = self._container.popitem(last=False) + + # After releasing the lock on the pool, dispose of any evicted value. + if evicted_item is not None and self.dispose_func: + _, evicted_value = evicted_item + self.dispose_func(evicted_value) + + def __delitem__(self, key: _KT) -> None: + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self) -> int: + with self.lock: + return len(self._container) + + def __iter__(self) -> typing.NoReturn: + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) + + def clear(self) -> None: + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(self._container.values()) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self) -> set[_KT]: # type: ignore[override] + with self.lock: + return set(self._container.keys()) + + +class HTTPHeaderDictItemView(set[tuple[str, str]]): + """ + HTTPHeaderDict is unusual for a Mapping[str, str] in that it has two modes of + address. + + If we directly try to get an item with a particular name, we will get a string + back that is the concatenated version of all the values: + + >>> d['X-Header-Name'] + 'Value1, Value2, Value3' + + However, if we iterate over an HTTPHeaderDict's items, we will optionally combine + these values based on whether combine=True was called when building up the dictionary + + >>> d = HTTPHeaderDict({"A": "1", "B": "foo"}) + >>> d.add("A", "2", combine=True) + >>> d.add("B", "bar") + >>> list(d.items()) + [ + ('A', '1, 2'), + ('B', 'foo'), + ('B', 'bar'), + ] + + This class conforms to the interface required by the MutableMapping ABC while + also giving us the nonstandard iteration behavior we want; items with duplicate + keys, ordered by time of first insertion. + """ + + _headers: HTTPHeaderDict + + def __init__(self, headers: HTTPHeaderDict) -> None: + self._headers = headers + + def __len__(self) -> int: + return len(list(self._headers.iteritems())) + + def __iter__(self) -> typing.Iterator[tuple[str, str]]: + return self._headers.iteritems() + + def __contains__(self, item: object) -> bool: + if isinstance(item, tuple) and len(item) == 2: + passed_key, passed_val = item + if isinstance(passed_key, str) and isinstance(passed_val, str): + return self._headers._has_value_for_header(passed_key, passed_val) + return False + + +class HTTPHeaderDict(typing.MutableMapping[str, str]): + """ + :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' + """ + + _container: typing.MutableMapping[str, list[str]] + + def __init__(self, headers: ValidHTTPHeaderSource | None = None, **kwargs: str): + super().__init__() + self._container = {} # 'dict' is insert-ordered + 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: str, val: str) -> None: + # avoid a bytes/str comparison by decoding before httplib + if isinstance(key, bytes): + key = key.decode("latin-1") + self._container[key.lower()] = [key, val] + + def __getitem__(self, key: str) -> str: + if isinstance(key, bytes): + key = key.decode("latin-1") + val = self._container[key.lower()] + return ", ".join(val[1:]) + + def __delitem__(self, key: str) -> None: + if isinstance(key, bytes): + key = key.decode("latin-1") + del self._container[key.lower()] + + def __contains__(self, key: object) -> bool: + if isinstance(key, bytes): + key = key.decode("latin-1") + if isinstance(key, str): + return key.lower() in self._container + return False + + def setdefault(self, key: str, default: str = "") -> str: + return super().setdefault(key, default) + + def __eq__(self, other: object) -> bool: + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return False + else: + other_as_http_header_dict = type(self)(maybe_constructable) + + return {k.lower(): v for k, v in self.itermerged()} == { + k.lower(): v for k, v in other_as_http_header_dict.itermerged() + } + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __len__(self) -> int: + return len(self._container) + + def __iter__(self) -> typing.Iterator[str]: + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def discard(self, key: str) -> None: + try: + del self[key] + except KeyError: + pass + + def add(self, key: str, val: str, *, combine: bool = False) -> None: + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + If this is called with combine=True, instead of adding a new header value + as a distinct item during iteration, this will instead append the value to + any existing header value with a comma. If no existing header value exists + for the key, then the value will simply be added, ignoring the combine parameter. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + >>> list(headers.items()) + [('foo', 'bar'), ('foo', 'baz')] + >>> headers.add('foo', 'quz', combine=True) + >>> list(headers.items()) + [('foo', 'bar, baz, quz')] + """ + # avoid a bytes/str comparison by decoding before httplib + if isinstance(key, bytes): + key = key.decode("latin-1") + 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: + # if there are values here, then there is at least the initial + # key/value pair + assert len(vals) >= 2 + if combine: + vals[-1] = vals[-1] + ", " + val + else: + vals.append(val) + + def extend(self, *args: ValidHTTPHeaderSource, **kwargs: str) -> None: + """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( + f"extend() takes at most 1 positional arguments ({len(args)} given)" + ) + 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, typing.Mapping): + for key, val in other.items(): + self.add(key, val) + elif isinstance(other, typing.Iterable): + for key, value in other: + self.add(key, value) + elif hasattr(other, "keys") and hasattr(other, "__getitem__"): + # THIS IS NOT A TYPESAFE BRANCH + # In this branch, the object has a `keys` attr but is not a Mapping or any of + # the other types indicated in the method signature. We do some stuff with + # it as though it partially implements the Mapping interface, but we're not + # doing that stuff safely AT ALL. + for key in other.keys(): + self.add(key, other[key]) + + for key, value in kwargs.items(): + self.add(key, value) + + @typing.overload + def getlist(self, key: str) -> list[str]: ... + + @typing.overload + def getlist(self, key: str, default: _DT) -> list[str] | _DT: ... + + def getlist( + self, key: str, default: _Sentinel | _DT = _Sentinel.not_passed + ) -> list[str] | _DT: + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + if isinstance(key, bytes): + key = key.decode("latin-1") + try: + vals = self._container[key.lower()] + except KeyError: + if default is _Sentinel.not_passed: + # _DT is unbound; empty list is instance of List[str] + return [] + # _DT is bound; default is instance of _DT + return default + else: + # _DT may or may not be bound; vals[1:] is instance of List[str], which + # meets our external interface requirement of `Union[List[str], _DT]`. + return vals[1:] + + def _prepare_for_method_change(self) -> Self: + """ + Remove content-specific header fields before changing the request + method to GET or HEAD according to RFC 9110, Section 15.4. + """ + content_specific_headers = [ + "Content-Encoding", + "Content-Language", + "Content-Location", + "Content-Type", + "Content-Length", + "Digest", + "Last-Modified", + ] + for header in content_specific_headers: + self.discard(header) + return self + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self) -> str: + return f"{type(self).__name__}({dict(self.itermerged())})" + + def _copy_from(self, other: HTTPHeaderDict) -> None: + for key in other: + val = other.getlist(key) + self._container[key.lower()] = [key, *val] + + def copy(self) -> Self: + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self) -> typing.Iterator[tuple[str, str]]: + """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) -> typing.Iterator[tuple[str, str]]: + """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) -> HTTPHeaderDictItemView: # type: ignore[override] + return HTTPHeaderDictItemView(self) + + def _has_value_for_header(self, header_name: str, potential_value: str) -> bool: + if header_name in self: + return potential_value in self._container[header_name.lower()][1:] + return False + + def __ior__(self, other: object) -> HTTPHeaderDict: + # Supports extending a header dict in-place using operator |= + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + self.extend(maybe_constructable) + return self + + def __or__(self, other: object) -> Self: + # Supports merging header dicts using operator | + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + result = self.copy() + result.extend(maybe_constructable) + return result + + def __ror__(self, other: object) -> Self: + # Supports merging header dicts using operator | when other is on left side + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + result = type(self)(maybe_constructable) + result.extend(self) + return result diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_request_methods.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_request_methods.py new file mode 100644 index 0000000000000000000000000000000000000000..297c271bf401c1cb48c6225f8822e78f58c3ca56 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_request_methods.py @@ -0,0 +1,278 @@ +from __future__ import annotations + +import json as _json +import typing +from urllib.parse import urlencode + +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .response import BaseHTTPResponse + +__all__ = ["RequestMethods"] + +_TYPE_ENCODE_URL_FIELDS = typing.Union[ + typing.Sequence[tuple[str, typing.Union[str, bytes]]], + typing.Mapping[str, typing.Union[str, bytes]], +] + + +class RequestMethods: + """ + 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: typing.Mapping[str, str] | None = None) -> None: + self.headers = headers or {} + + def urlopen( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + encode_multipart: bool = True, + multipart_boundary: str | None = None, + **kw: typing.Any, + ) -> BaseHTTPResponse: # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + json: typing.Any | None = None, + **urlopen_kw: typing.Any, + ) -> BaseHTTPResponse: + """ + 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`. + + :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 fields: + Data to encode and send in the URL or request body, depending on ``method``. + + :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 json: + Data to encode and send as JSON with UTF-encoded in the request body. + The ``"Content-Type"`` header will be set to ``"application/json"`` + unless specified otherwise. + """ + method = method.upper() + + if json is not None and body is not None: + raise TypeError( + "request got values for both 'body' and 'json' parameters which are mutually exclusive" + ) + + if json is not None: + if headers is None: + headers = self.headers + + if not ("content-type" in map(str.lower, headers.keys())): + headers = HTTPHeaderDict(headers) + headers["Content-Type"] = "application/json" + + body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode( + "utf-8" + ) + + if body is not None: + urlopen_kw["body"] = body + + if method in self._encode_url_methods: + return self.request_encode_url( + method, + url, + fields=fields, # type: ignore[arg-type] + headers=headers, + **urlopen_kw, + ) + else: + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + + def request_encode_url( + self, + method: str, + url: str, + fields: _TYPE_ENCODE_URL_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + **urlopen_kw: str, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param fields: + Data to encode and send in the URL. + + :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. + """ + if headers is None: + headers = self.headers + + extra_kw: dict[str, typing.Any] = {"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: str, + url: str, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + encode_multipart: bool = True, + multipart_boundary: str | None = None, + **urlopen_kw: str, + ) -> BaseHTTPResponse: + """ + 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. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param fields: + Data to encode and send in the request body. + + :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 encode_multipart: + If True, encode the ``fields`` using the multipart/form-data MIME + format. + + :param multipart_boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + if headers is None: + headers = self.headers + + extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)} + body: bytes | str + + 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), # type: ignore[arg-type] + "application/x-www-form-urlencoded", + ) + + extra_kw["body"] = body + extra_kw["headers"].setdefault("Content-Type", content_type) + + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_version.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..bfed03985b436e778ef0caa2703c8a6774dda483 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/_version.py @@ -0,0 +1,24 @@ +# file generated by vcs-versioning +# don't change, don't track in version control +from __future__ import annotations + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +version: str +__version__: str +__version_tuple__: tuple[int | str, ...] +version_tuple: tuple[int | str, ...] +commit_id: str | None +__commit_id__: str | None + +__version__ = version = '2.7.0' +__version_tuple__ = version_tuple = (2, 7, 0) + +__commit_id__ = commit_id = None diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/connection.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..84e1dab9452d18ad0a2020c55f1966f4920f56c2 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/connection.py @@ -0,0 +1,1099 @@ +from __future__ import annotations + +import datetime +import http.client +import logging +import os +import re +import socket +import sys +import threading +import typing +import warnings +from http.client import HTTPConnection as _HTTPConnection +from http.client import HTTPException as HTTPException # noqa: F401 +from http.client import ResponseNotReady +from socket import timeout as SocketTimeout + +if typing.TYPE_CHECKING: + from .response import HTTPResponse + from .util.ssl_ import _TYPE_PEER_CERT_RET_DICT + from .util.ssltransport import SSLTransport + +from ._collections import HTTPHeaderDict +from .http2 import probe as http2_probe +from .util.response import assert_header_parsing +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT, Timeout +from .util.util import to_str +from .util.wait import wait_for_read + +try: # Compiled with SSL? + import ssl + + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): + ssl = None # type: ignore[assignment] + + class BaseSSLError(BaseException): # type: ignore[no-redef] + pass + + +from ._base_connection import _TYPE_BODY +from ._base_connection import ProxyConfig as ProxyConfig +from ._base_connection import _ResponseOptions as _ResponseOptions +from ._version import __version__ +from .exceptions import ( + ConnectTimeoutError, + HeaderParsingError, + NameResolutionError, + NewConnectionError, + ProxyError, + SystemTimeWarning, +) +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_ +from .util.request import body_to_chunks +from .util.ssl_ import assert_fingerprint as _assert_fingerprint +from .util.ssl_ import ( + create_urllib3_context, + is_ipaddress, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .util.ssl_match_hostname import CertificateError, match_hostname +from .util.url import Url + +# Not a no-op, we're adding this to the namespace so it can be imported. +ConnectionError = ConnectionError +BrokenPipeError = BrokenPipeError + + +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(2025, 1, 1) + +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") + + +class HTTPConnection(_HTTPConnection): + """ + 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: + + - ``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: typing.ClassVar[int] = port_by_scheme["http"] # type: ignore[misc] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options: typing.ClassVar[connection._TYPE_SOCKET_OPTIONS] = [ + (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + ] + + #: Whether this connection verifies the host's certificate. + is_verified: bool = False + + #: Whether this proxy connection verified the proxy host's certificate. + # If no proxy is currently connected to the value will be ``None``. + proxy_is_verified: bool | None = None + + blocksize: int + source_address: tuple[str, int] | None + socket_options: connection._TYPE_SOCKET_OPTIONS | None + + _has_connected_to_proxy: bool + _response_options: _ResponseOptions | None + _tunnel_host: str | None + _tunnel_port: int | None + _tunnel_scheme: str | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: None | ( + connection._TYPE_SOCKET_OPTIONS + ) = default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: + super().__init__( + host=host, + port=port, + timeout=Timeout.resolve_default_timeout(timeout), + source_address=source_address, + blocksize=blocksize, + ) + self.socket_options = socket_options + self.proxy = proxy + self.proxy_config = proxy_config + + self._has_connected_to_proxy = False + self._response_options = None + self._tunnel_host: str | None = None + self._tunnel_port: int | None = None + self._tunnel_scheme: str | None = None + + def __str__(self) -> str: + return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" + + def __repr__(self) -> str: + return f"<{self} at {id(self):#x}>" + + @property + def host(self) -> str: + """ + 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: str) -> None: + """ + 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) -> socket.socket: + """Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + try: + sock = connection.create_connection( + (self._dns_host, self.port), + self.timeout, + source_address=self.source_address, + socket_options=self.socket_options, + ) + except socket.gaierror as e: + raise NameResolutionError(self.host, self, e) from e + except SocketTimeout as e: + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + + except OSError as e: + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + sys.audit("http.client.connect", self, self.host, self.port) + + return sock + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + if scheme not in ("http", "https"): + raise ValueError( + f"Invalid proxy scheme for tunneling: {scheme!r}, must be either 'http' or 'https'" + ) + super().set_tunnel(host, port=port, headers=headers) + self._tunnel_scheme = scheme + + if sys.version_info < (3, 11, 9) or ((3, 12) <= sys.version_info < (3, 12, 3)): + # Taken from python/cpython#100986 which was backported in 3.11.9 and 3.12.3. + # When using connection_from_host, host will come without brackets. + def _wrap_ipv6(self, ip: bytes) -> bytes: + if b":" in ip and ip[0] != b"["[0]: + return b"[" + ip + b"]" + return ip + + if sys.version_info < (3, 11, 9): + # `_tunnel` copied from 3.11.13 backporting + # https://github.com/python/cpython/commit/0d4026432591d43185568dd31cef6a034c4b9261 + # and https://github.com/python/cpython/commit/6fbc61070fda2ffb8889e77e3b24bca4249ab4d1 + def _tunnel(self) -> None: + _MAXLINE = http.client._MAXLINE # type: ignore[attr-defined] + connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( # type: ignore[str-format] + self._wrap_ipv6(self._tunnel_host.encode("ascii")), # type: ignore[union-attr] + self._tunnel_port, + ) + headers = [connect] + for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] + headers.append(f"{header}: {value}\r\n".encode("latin-1")) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of + # potentially emitting a series of small packets. + self.send(b"".join(headers)) + del headers + + response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] + try: + (version, code, message) = response._read_status() # type: ignore[attr-defined] + + if code != http.HTTPStatus.OK: + self.close() + raise OSError( + f"Tunnel connection failed: {code} {message.strip()}" + ) + while True: + line = response.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise http.client.LineTooLong("header line") + if not line: + # for sites which EOF without sending a trailer + break + if line in (b"\r\n", b"\n", b""): + break + + if self.debuglevel > 0: + print("header:", line.decode()) + finally: + response.close() + + elif (3, 12) <= sys.version_info < (3, 12, 3): + # `_tunnel` copied from 3.12.11 backporting + # https://github.com/python/cpython/commit/23aef575c7629abcd4aaf028ebd226fb41a4b3c8 + def _tunnel(self) -> None: # noqa: F811 + connect = b"CONNECT %s:%d HTTP/1.1\r\n" % ( # type: ignore[str-format] + self._wrap_ipv6(self._tunnel_host.encode("idna")), # type: ignore[union-attr] + self._tunnel_port, + ) + headers = [connect] + for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] + headers.append(f"{header}: {value}\r\n".encode("latin-1")) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of + # potentially emitting a series of small packets. + self.send(b"".join(headers)) + del headers + + response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] + try: + (version, code, message) = response._read_status() # type: ignore[attr-defined] + + self._raw_proxy_headers = http.client._read_headers(response.fp) # type: ignore[attr-defined] + + if self.debuglevel > 0: + for header in self._raw_proxy_headers: + print("header:", header.decode()) + + if code != http.HTTPStatus.OK: + self.close() + raise OSError( + f"Tunnel connection failed: {code} {message.strip()}" + ) + + finally: + response.close() + + def connect(self) -> None: + self.sock = self._new_conn() + if self._tunnel_host: + # If we're tunneling it means we're connected to our proxy. + self._has_connected_to_proxy = True + + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + + # If there's a proxy to be connected to we are fully connected. + # This is set twice (once above and here) due to forwarding proxies + # not using tunnelling. + self._has_connected_to_proxy = bool(self.proxy) + + if self._has_connected_to_proxy: + self.proxy_is_verified = False + + @property + def is_closed(self) -> bool: + return self.sock is None + + @property + def is_connected(self) -> bool: + if self.sock is None: + return False + return not wait_for_read(self.sock, timeout=0.0) + + @property + def has_connected_to_proxy(self) -> bool: + return self._has_connected_to_proxy + + @property + def proxy_is_forwarding(self) -> bool: + """ + Return True if a forwarding proxy is configured, else return False + """ + return bool(self.proxy) and self._tunnel_host is None + + @property + def proxy_is_tunneling(self) -> bool: + """ + Return True if a tunneling proxy is configured, else return False + """ + return self._tunnel_host is not None + + def close(self) -> None: + try: + super().close() + finally: + # Reset all stateful properties so connection + # can be re-used without leaking prior configs. + self.sock = None + self.is_verified = False + self.proxy_is_verified = None + self._has_connected_to_proxy = False + self._response_options = None + self._tunnel_host = None + self._tunnel_port = None + self._tunnel_scheme = None + + def putrequest( + self, + method: str, + url: str, + skip_host: bool = False, + skip_accept_encoding: bool = False, + ) -> None: + """""" + # 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( + f"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})" + ) + + return super().putrequest( + method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding + ) + + def putheader(self, header: str, *values: str) -> None: # type: ignore[override] + """""" + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + super().putheader(header, *values) + elif to_str(header.lower()) not in SKIPPABLE_HEADERS: + skippable_headers = "', '".join( + [str.title(header) for header in sorted(SKIPPABLE_HEADERS)] + ) + raise ValueError( + f"urllib3.util.SKIP_HEADER only supports '{skippable_headers}'" + ) + + # `request` method's signature intentionally violates LSP. + # urllib3's API is different from `http.client.HTTPConnection` and the subclassing is only incidental. + def request( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: + # Update the inner socket's timeout value to send the request. + # This only triggers if the connection is re-used. + if self.sock is not None: + self.sock.settimeout(self.timeout) + + # Store these values to be fed into the HTTPResponse + # object later. TODO: Remove this in favor of a real + # HTTP lifecycle mechanism. + + # We have to store these before we call .request() + # because sometimes we can still salvage a response + # off the wire even if we aren't able to completely + # send the request body. + self._response_options = _ResponseOptions( + request_method=method, + request_url=url, + preload_content=preload_content, + decode_content=decode_content, + enforce_content_length=enforce_content_length, + ) + + if headers is None: + headers = {} + header_keys = frozenset(to_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 + ) + + # Transform the body into an iterable of sendall()-able chunks + # and detect if an explicit Content-Length is doable. + chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize) + chunks = chunks_and_cl.chunks + content_length = chunks_and_cl.content_length + + # When chunked is explicit set to 'True' we respect that. + if chunked: + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") + else: + # Detect whether a framing mechanism is already in use. If so + # we respect that value, otherwise we pick chunked vs content-length + # depending on the type of 'body'. + if "content-length" in header_keys: + chunked = False + elif "transfer-encoding" in header_keys: + chunked = True + + # Otherwise we go off the recommendation of 'body_to_chunks()'. + else: + chunked = False + if content_length is None: + if chunks is not None: + chunked = True + self.putheader("Transfer-Encoding", "chunked") + else: + self.putheader("Content-Length", str(content_length)) + + # Now that framing headers are out of the way we send all the other headers. + 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) + self.endheaders() + + # If we're given a body we start sending that in chunks. + if chunks is not None: + for chunk in chunks: + # Sending empty chunks isn't allowed for TE: chunked + # as it indicates the end of the body. + if not chunk: + continue + if isinstance(chunk, str): + chunk = chunk.encode("utf-8") + if chunked: + self.send(b"%x\r\n%b\r\n" % (len(chunk), chunk)) + else: + self.send(chunk) + + # Regardless of whether we have a body or not, if we're in + # chunked mode we want to send an explicit empty chunk. + if chunked: + self.send(b"0\r\n\r\n") + + def request_chunked( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + ) -> None: + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + warnings.warn( + "HTTPConnection.request_chunked() is deprecated and will be removed " + "in urllib3 v3.0. Instead use HTTPConnection.request(..., chunked=True).", + category=FutureWarning, + stacklevel=2, + ) + self.request(method, url, body=body, headers=headers, chunked=True) + + def getresponse( # type: ignore[override] + self, + ) -> HTTPResponse: + """ + Get the response from the server. + + If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable. + + If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP response indicates that the connection should be closed, then it will be closed before the response is returned. When the connection is closed, the underlying socket is closed. + """ + # Raise the same error as http.client.HTTPConnection + if self._response_options is None: + raise ResponseNotReady() + + # Reset this attribute for being used again. + resp_options = self._response_options + self._response_options = None + + # Since the connection's timeout value may have been updated + # we need to set the timeout on the socket. + self.sock.settimeout(self.timeout) + + # This is needed here to avoid circular import errors + from .response import HTTPResponse + + # Save a reference to the shutdown function before ownership is passed + # to httplib_response + # TODO should we implement it everywhere? + _shutdown = getattr(self.sock, "shutdown", None) + + # Get the response from http.client.HTTPConnection + httplib_response = super().getresponse() + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: + log.warning( + "Failed to parse headers (url=%s): %s", + _url_from_connection(self, resp_options.request_url), + hpe, + exc_info=True, + ) + + headers = HTTPHeaderDict(httplib_response.msg.items()) + + response = HTTPResponse( + body=httplib_response, + headers=headers, + status=httplib_response.status, + version=httplib_response.version, + version_string=getattr(self, "_http_vsn_str", "HTTP/?"), + reason=httplib_response.reason, + preload_content=resp_options.preload_content, + decode_content=resp_options.decode_content, + original_response=httplib_response, + enforce_content_length=resp_options.enforce_content_length, + request_method=resp_options.request_method, + request_url=resp_options.request_url, + sock_shutdown=_shutdown, + ) + return response + + +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"] # type: ignore[misc] + + cert_reqs: int | str | None = None + ca_certs: str | None = None + ca_cert_dir: str | None = None + ca_cert_data: None | str | bytes = None + ssl_version: int | str | None = None + ssl_minimum_version: int | None = None + ssl_maximum_version: int | None = None + assert_fingerprint: str | None = None + _connect_callback: typing.Callable[..., None] | None = None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: None | ( + connection._TYPE_SOCKET_OPTIONS + ) = HTTPConnection.default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: + super().__init__( + host, + port=port, + timeout=timeout, + source_address=source_address, + blocksize=blocksize, + socket_options=socket_options, + proxy=proxy, + proxy_config=proxy_config, + ) + + 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 + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + 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 + + # cert_reqs depends on ssl_context so calculate last. + 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.cert_reqs = cert_reqs + self._connect_callback = None + + def set_cert( + self, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ) -> None: + """ + This method should only be called once, before the connection is used. + """ + warnings.warn( + "HTTPSConnection.set_cert() is deprecated and will be removed " + "in urllib3 v3.0. Instead provide the parameters to the " + "HTTPSConnection constructor.", + category=FutureWarning, + stacklevel=2, + ) + + # 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) -> None: + # Today we don't need to be doing this step before the /actual/ socket + # connection, however in the future we'll need to decide whether to + # create a new socket or re-use an existing "shared" socket as a part + # of the HTTP/2 handshake dance. + if self._tunnel_host is not None and self._tunnel_port is not None: + probe_http2_host = self._tunnel_host + probe_http2_port = self._tunnel_port + else: + probe_http2_host = self.host + probe_http2_port = self.port + + # Check if the target origin supports HTTP/2. + # If the value comes back as 'None' it means that the current thread + # is probing for HTTP/2 support. Otherwise, we're waiting for another + # probe to complete, or we get a value right away. + target_supports_http2: bool | None + if "h2" in ssl_.ALPN_PROTOCOLS: + target_supports_http2 = http2_probe.acquire_and_get( + host=probe_http2_host, port=probe_http2_port + ) + else: + # If HTTP/2 isn't going to be offered it doesn't matter if + # the target supports HTTP/2. Don't want to make a probe. + target_supports_http2 = False + + if self._connect_callback is not None: + self._connect_callback( + "before connect", + thread_id=threading.get_ident(), + target_supports_http2=target_supports_http2, + ) + + try: + sock: socket.socket | ssl.SSLSocket + self.sock = sock = self._new_conn() + server_hostname: str = self.host + tls_in_tls = False + + # Do we need to establish a tunnel? + if self.proxy_is_tunneling: + # We're tunneling to an HTTPS origin so need to do TLS-in-TLS. + if self._tunnel_scheme == "https": + # _connect_tls_proxy will verify and assign proxy_is_verified + self.sock = sock = self._connect_tls_proxy(self.host, sock) + tls_in_tls = True + elif self._tunnel_scheme == "http": + self.proxy_is_verified = False + + # If we're tunneling it means we're connected to our proxy. + self._has_connected_to_proxy = True + + self._tunnel() + # Override the host with the one we're requesting data from. + server_hostname = typing.cast(str, self._tunnel_host) + + 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( + ( + f"System time is way off (before {RECENT_DATE}). This will probably " + "lead to SSL verification errors" + ), + SystemTimeWarning, + ) + + # Remove trailing '.' from fqdn hostnames to allow certificate validation + server_hostname_rm_dot = server_hostname.rstrip(".") + + sock_and_verified = _ssl_wrap_socket_and_match_hostname( + sock=sock, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + server_hostname=server_hostname_rm_dot, + ssl_context=self.ssl_context, + tls_in_tls=tls_in_tls, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + self.sock = sock_and_verified.socket + + # If an error occurs during connection/handshake we may need to release + # our lock so another connection can probe the origin. + except BaseException: + if self._connect_callback is not None: + self._connect_callback( + "after connect failure", + thread_id=threading.get_ident(), + target_supports_http2=target_supports_http2, + ) + + if target_supports_http2 is None: + http2_probe.set_and_release( + host=probe_http2_host, port=probe_http2_port, supports_http2=None + ) + raise + + # If this connection doesn't know if the origin supports HTTP/2 + # we report back to the HTTP/2 probe our result. + if target_supports_http2 is None: + supports_http2 = sock_and_verified.socket.selected_alpn_protocol() == "h2" + http2_probe.set_and_release( + host=probe_http2_host, + port=probe_http2_port, + supports_http2=supports_http2, + ) + + # Forwarding proxies can never have a verified target since + # the proxy is the one doing the verification. Should instead + # use a CONNECT tunnel in order to verify the target. + # See: https://github.com/urllib3/urllib3/issues/3267. + if self.proxy_is_forwarding: + self.is_verified = False + else: + self.is_verified = sock_and_verified.is_verified + + # If there's a proxy to be connected to we are fully connected. + # This is set twice (once above and here) due to forwarding proxies + # not using tunnelling. + self._has_connected_to_proxy = bool(self.proxy) + + # Set `self.proxy_is_verified` unless it's already set while + # establishing a tunnel. + if self._has_connected_to_proxy and self.proxy_is_verified is None: + self.proxy_is_verified = sock_and_verified.is_verified + + def _connect_tls_proxy(self, hostname: str, sock: socket.socket) -> ssl.SSLSocket: + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + # `_connect_tls_proxy` is called when self._tunnel_host is truthy. + proxy_config = typing.cast(ProxyConfig, self.proxy_config) + ssl_context = proxy_config.ssl_context + sock_and_verified = _ssl_wrap_socket_and_match_hostname( + sock, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + 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, + assert_hostname=proxy_config.assert_hostname, + assert_fingerprint=proxy_config.assert_fingerprint, + # Features that aren't implemented for proxies yet: + cert_file=None, + key_file=None, + key_password=None, + tls_in_tls=False, + ) + self.proxy_is_verified = sock_and_verified.is_verified + return sock_and_verified.socket # type: ignore[return-value] + + +class _WrappedAndVerifiedSocket(typing.NamedTuple): + """ + Wrapped socket and whether the connection is + verified after the TLS handshake + """ + + socket: ssl.SSLSocket | SSLTransport + is_verified: bool + + +def _ssl_wrap_socket_and_match_hostname( + sock: socket.socket, + *, + cert_reqs: None | str | int, + ssl_version: None | str | int, + ssl_minimum_version: int | None, + ssl_maximum_version: int | None, + cert_file: str | None, + key_file: str | None, + key_password: str | None, + ca_certs: str | None, + ca_cert_dir: str | None, + ca_cert_data: None | str | bytes, + assert_hostname: None | str | typing.Literal[False], + assert_fingerprint: str | None, + server_hostname: str | None, + ssl_context: ssl.SSLContext | None, + tls_in_tls: bool = False, +) -> _WrappedAndVerifiedSocket: + """Logic for constructing an SSLContext from all TLS parameters, passing + that down into ssl_wrap_socket, and then doing certificate verification + either via hostname or fingerprint. This function exists to guarantee + that both proxies and targets have the same behavior when connecting via TLS. + """ + default_ssl_context = False + if ssl_context is None: + default_ssl_context = True + context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + ssl_minimum_version=ssl_minimum_version, + ssl_maximum_version=ssl_maximum_version, + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + else: + context = ssl_context + + context.verify_mode = resolve_cert_reqs(cert_reqs) + + # In some cases, we want to verify hostnames ourselves + if ( + # `ssl` can't verify fingerprints or alternate hostnames + assert_fingerprint + or assert_hostname + # assert_hostname can be set to False to disable hostname checking + or assert_hostname is False + # We still support OpenSSL 1.0.2, which prevents us from verifying + # hostnames easily: https://github.com/pyca/pyopenssl/pull/933 + or ssl_.IS_PYOPENSSL + or not ssl_.HAS_NEVER_CHECK_COMMON_NAME + ): + context.check_hostname = False + + # Try to load OS default certs if none are given. We need to do the hasattr() check + # for custom pyOpenSSL SSLContext objects because they don't support + # load_default_certs(). + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + + # Ensure that IPv6 addresses are in the proper format and don't have a + # scope ID. Python's SSL module fails to recognize scoped IPv6 addresses + # and interprets them as DNS hostnames. + if server_hostname is not None: + normalized = server_hostname.strip("[]") + if "%" in normalized: + normalized = normalized[: normalized.rfind("%")] + if is_ipaddress(normalized): + server_hostname = normalized + + ssl_sock = ssl_wrap_socket( + sock=sock, + keyfile=key_file, + certfile=cert_file, + key_password=key_password, + ca_certs=ca_certs, + ca_cert_dir=ca_cert_dir, + ca_cert_data=ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + try: + if assert_fingerprint: + _assert_fingerprint( + ssl_sock.getpeercert(binary_form=True), assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not context.check_hostname + and assert_hostname is not False + ): + cert: _TYPE_PEER_CERT_RET_DICT = ssl_sock.getpeercert() # type: ignore[assignment] + + # Need to signal to our match_hostname whether to use 'commonName' or not. + # If we're using our own constructed SSLContext we explicitly set 'False' + # because PyPy hard-codes 'True' from SSLContext.hostname_checks_common_name. + if default_ssl_context: + hostname_checks_common_name = False + else: + hostname_checks_common_name = ( + getattr(context, "hostname_checks_common_name", False) or False + ) + + _match_hostname( + cert, + assert_hostname or server_hostname, # type: ignore[arg-type] + hostname_checks_common_name, + ) + + return _WrappedAndVerifiedSocket( + socket=ssl_sock, + is_verified=context.verify_mode == ssl.CERT_REQUIRED + or bool(assert_fingerprint), + ) + except BaseException: + ssl_sock.close() + raise + + +def _match_hostname( + cert: _TYPE_PEER_CERT_RET_DICT | None, + asserted_hostname: str, + hostname_checks_common_name: bool = False, +) -> None: + # 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("[]") + if is_ipaddress(stripped_hostname): + asserted_hostname = stripped_hostname + + try: + match_hostname(cert, asserted_hostname, hostname_checks_common_name) + 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 # type: ignore[attr-defined] + raise + + +def _wrap_proxy_error(err: Exception, proxy_scheme: str | None) -> ProxyError: + # Look for the phrase 'wrong version number', if found + # then we should warn the user that we're very sure that + # this proxy is HTTP-only and they have a configuration issue. + error_normalized = " ".join(re.split("[^a-z]", str(err).lower())) + is_likely_http_proxy = ( + "wrong version number" in error_normalized + or "unknown protocol" in error_normalized + or "record layer failure" in error_normalized + ) + http_proxy_warning = ( + ". Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#https-proxy-error-http-proxy" + ) + new_err = ProxyError( + f"Unable to connect to proxy" + f"{http_proxy_warning if is_likely_http_proxy and proxy_scheme == 'https' else ''}", + err, + ) + new_err.__cause__ = err + return new_err + + +def _get_default_user_agent() -> str: + return f"python-urllib3/{__version__}" + + +class DummyConnection: + """Used to detect a failed ConnectionCls import.""" + + +if not ssl: + HTTPSConnection = DummyConnection # type: ignore[misc, assignment] # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection + + +def _url_from_connection( + conn: HTTPConnection | HTTPSConnection, path: str | None = None +) -> str: + """Returns the URL from a given connection. This is mainly used for testing and logging.""" + + scheme = "https" if isinstance(conn, HTTPSConnection) else "http" + + return Url(scheme=scheme, host=conn.host, port=conn.port, path=path).url diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/connectionpool.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/connectionpool.py new file mode 100644 index 0000000000000000000000000000000000000000..70fbc5e725aee571654b1a58748537fa167b498d --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/connectionpool.py @@ -0,0 +1,1191 @@ +from __future__ import annotations + +import errno +import logging +import queue +import sys +import typing +import warnings +import weakref +from socket import timeout as SocketTimeout +from types import TracebackType + +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from ._request_methods import RequestMethods +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + ProxyConfig, + _wrap_proxy_error, +) +from .connection import port_by_scheme as port_by_scheme +from .exceptions import ( + ClosedPoolError, + EmptyPoolError, + FullPoolError, + HostChangedError, + InsecureRequestWarning, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, +) +from .response import BaseHTTPResponse +from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.request import _TYPE_BODY_POSITION, set_file_position +from .util.retry import Retry +from .util.ssl_match_hostname import CertificateError +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_DEFAULT, Timeout +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import parse_url +from .util.util import to_str + +if typing.TYPE_CHECKING: + import ssl + + from typing_extensions import Self + + from ._base_connection import BaseHTTPConnection, BaseHTTPSConnection + +log = logging.getLogger(__name__) + +_TYPE_TIMEOUT = typing.Union[Timeout, float, _TYPE_DEFAULT, None] + + +# Pool objects +class ConnectionPool: + """ + 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: str | None = None + QueueCls = queue.LifoQueue + + def __init__(self, host: str, port: int | None = None) -> None: + if not host: + raise LocationValueError("No host specified.") + + self.host = _normalize_host(host, scheme=self.scheme) + self.port = port + + # This property uses 'normalize_host()' (not '_normalize_host()') + # to avoid removing square braces around IPv6 addresses. + # This value is sent to `HTTPConnection.set_tunnel()` if called + # because square braces are required for HTTP CONNECT tunneling. + self._tunnel_host = normalize_host(host, scheme=self.scheme).lower() + + def __str__(self) -> str: + return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> typing.Literal[False]: + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self) -> None: + """ + Close all pooled connections and disable the pool. + """ + + +# 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 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: type[BaseHTTPConnection] | type[BaseHTTPSConnection] = HTTPConnection + + def __init__( + self, + host: str, + port: int | None = None, + timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, + maxsize: int = 1, + block: bool = False, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + _proxy: Url | None = None, + _proxy_headers: typing.Mapping[str, str] | None = None, + _proxy_config: ProxyConfig | None = None, + **conn_kw: typing.Any, + ): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + 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: queue.LifoQueue[typing.Any] | None = 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 range(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. + # Defaulting `socket_options` to an empty list avoids it defaulting to + # ``HTTPConnection.default_socket_options``. + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config + + # Do not pass 'self' as callback to 'finalize'. + # Then the 'finalize' would keep an endless living (leak) to self. + # By just passing a reference to the pool allows the garbage collector + # to free self if nobody else has a reference to it. + pool = self.pool + + # Close all the HTTPConnections in the pool before the + # HTTPConnectionPool object is garbage collected. + weakref.finalize(self, _close_pool_connections, pool) + + def _new_conn(self) -> BaseHTTPConnection: + """ + 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, + **self.conn_kw, + ) + return conn + + def _get_conn(self, timeout: float | None = None) -> BaseHTTPConnection: + """ + 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 + + if self.pool is None: + raise ClosedPoolError(self, "Pool is closed.") + + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") from None # Defensive: + + except queue.Empty: + if self.block: + raise EmptyPoolError( + self, + "Pool is empty and a new connection can't be opened due to blocking mode.", + ) from None + 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() + + return conn or self._new_conn() + + def _put_conn(self, conn: BaseHTTPConnection | None) -> None: + """ + 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. + """ + if self.pool is not None: + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + if self.block: + # This should never happen if you got the conn from self._get_conn + raise FullPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) from None + + 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: BaseHTTPConnection) -> None: + """ + Called right before a request is made, after the socket is created. + """ + + def _prepare_proxy(self, conn: BaseHTTPConnection) -> None: + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout: _TYPE_TIMEOUT) -> Timeout: + """Helper that always returns a :class:`urllib3.util.Timeout`""" + if timeout is _DEFAULT_TIMEOUT: + 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: BaseSSLError | OSError | SocketTimeout, + url: str, + timeout_value: _TYPE_TIMEOUT | None, + ) -> None: + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={timeout_value})" + ) from err + + # See the above comment about EAGAIN in Python 3. + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={timeout_value})" + ) from err + + def _make_request( + self, + conn: BaseHTTPConnection, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | None = None, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + chunked: bool = False, + response_conn: BaseHTTPConnection | None = None, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> BaseHTTPResponse: + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :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 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 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 response_conn: + Set this to ``None`` if you will handle releasing the connection or + set the connection to have the response release it. + + :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 enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout) + + try: + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # _validate_conn() starts the connection to an HTTPS proxy + # so we need to wrap errors with 'ProxyError' here too. + except ( + OSError, + NewConnectionError, + TimeoutError, + BaseSSLError, + CertificateError, + SSLError, + ) as e: + new_e: Exception = e + if isinstance(e, (BaseSSLError, CertificateError)): + new_e = SSLError(e) + # If the connection didn't successfully connect to it's proxy + # then there + if isinstance( + new_e, (OSError, NewConnectionError, TimeoutError, SSLError) + ) and (conn and conn.proxy and not conn.has_connected_to_proxy): + new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) + raise new_e + + # conn.request() calls http.client.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + try: + conn.request( + method, + url, + body=body, + headers=headers, + chunked=chunked, + preload_content=preload_content, + decode_content=decode_content, + enforce_content_length=enforce_content_length, + ) + + # 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: + pass + except OSError as e: + # MacOS/Linux + # EPROTOTYPE and ECONNRESET are needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + # Condition changed later to emit ECONNRESET instead of only EPROTOTYPE. + if e.errno != errno.EPROTOTYPE and e.errno != errno.ECONNRESET: + raise + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + if not conn.is_closed: + # 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, f"Read timed out. (read timeout={read_timeout})" + ) + conn.timeout = read_timeout + + # Receive the response from the server + try: + response = conn.getresponse() + except (BaseSSLError, OSError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # Set properties that are used by the pooling layer. + response.retries = retries + response._connection = response_conn # type: ignore[attr-defined] + response._pool = self # type: ignore[attr-defined] + + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + response.version_string, + response.status, + response.length_remaining, + ) + + return response + + def close(self) -> None: + """ + 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 + + # Close all the HTTPConnections in the pool. + _close_pool_connections(old_pool) + + def is_same_host(self, url: str) -> bool: + """ + 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, *_ = parse_url(url) + scheme = scheme or "http" + 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( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + redirect: bool = True, + assert_same_host: bool = True, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + pool_timeout: int | None = None, + release_conn: bool | None = None, + chunked: bool = False, + body_pos: _TYPE_BODY_POSITION | None = None, + preload_content: bool = True, + decode_content: bool = True, + **response_kw: typing.Any, + ) -> BaseHTTPResponse: + """ + 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 + 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. + + If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. 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 bool preload_content: + If True, the response's body will be preloaded into memory. + + :param bool decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :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 ``preload_content`` + which defaults to ``True``. + + :param bool 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. + """ + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + # URLs starting with / are inherently schemeless. + url = to_str(_encode_target(url)) + destination_scheme = None + else: + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + url = to_str(parsed_url.url) + + 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 = preload_content + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + 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() # type: ignore[attr-defined] + headers.update(self.proxy_headers) # type: ignore[union-attr] + + # 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 # type: ignore[assignment] + + # Is this a closed/new connection that requires CONNECT tunnelling? + if self.proxy is not None and http_tunnel_required and conn.is_closed: + try: + self._prepare_proxy(conn) + except (BaseSSLError, OSError, SocketTimeout) as e: + self._raise_timeout( + err=e, url=self.proxy.url, timeout_value=conn.timeout + ) + raise + + # 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 + + # Make the request on the HTTPConnection object + response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + retries=retries, + response_conn=response_conn, + preload_content=preload_content, + decode_content=decode_content, + **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, + OSError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ProxyError, + ) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + new_e: Exception = e + if isinstance(e, (BaseSSLError, CertificateError)): + new_e = SSLError(e) + if isinstance( + new_e, + ( + OSError, + NewConnectionError, + TimeoutError, + SSLError, + HTTPException, + ), + ) and (conn and conn.proxy and not conn.has_connected_to_proxy): + new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) + elif isinstance(new_e, (OSError, HTTPException)): + new_e = ProtocolError("Connection aborted.", new_e) + + retries = retries.increment( + method, url, error=new_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. + if conn: + conn.close() + conn = None + 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, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + # Change the method according to RFC 9110, Section 15.4.4. + method = "GET" + # And lose the body not to transfer anything sensitive. + body = None + headers = HTTPHeaderDict(headers)._prepare_for_method_change() + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # self.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not self.is_same_host( + redirect_location + ): + new_headers = headers.copy() # type: ignore[union-attr] + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + new_headers.pop(header, None) + headers = new_headers + + 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, + preload_content=preload_content, + decode_content=decode_content, + **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, + preload_content=preload_content, + decode_content=decode_content, + **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: type[BaseHTTPSConnection] = HTTPSConnection + + def __init__( + self, + host: str, + port: int | None = None, + timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, + maxsize: int = 1, + block: bool = False, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + _proxy: Url | None = None, + _proxy_headers: typing.Mapping[str, str] | None = None, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + ssl_version: int | str | None = None, + ssl_minimum_version: ssl.TLSVersion | None = None, + ssl_maximum_version: ssl.TLSVersion | None = None, + assert_hostname: str | typing.Literal[False] | None = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + **conn_kw: typing.Any, + ) -> None: + super().__init__( + host, + port, + 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.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_proxy(self, conn: HTTPSConnection) -> None: # type: ignore[override] + """Establishes a tunnel connection through HTTP CONNECT.""" + if self.proxy and self.proxy.scheme == "https": + tunnel_scheme = "https" + else: + tunnel_scheme = "http" + + conn.set_tunnel( + scheme=tunnel_scheme, + host=self._tunnel_host, + port=self.port, + headers=self.proxy_headers, + ) + conn.connect() + + def _new_conn(self) -> BaseHTTPSConnection: + """ + Return a fresh :class:`urllib3.connection.HTTPConnection`. + """ + 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: # type: ignore[comparison-overlap] + raise ImportError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) + + actual_host: str = self.host + actual_port = self.port + if self.proxy is not None and self.proxy.host is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + return self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + 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, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + **self.conn_kw, + ) + + def _validate_conn(self, conn: BaseHTTPConnection) -> None: + """ + Called right before a request is made, after the socket is created. + """ + super()._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if conn.is_closed: + conn.connect() + + # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791 + if not conn.is_verified and not conn.proxy_is_verified: + warnings.warn( + ( + f"Unverified HTTPS request is being made to host '{conn.host}'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#tls-warnings" + ), + InsecureRequestWarning, + ) + + +def connection_from_url(url: str, **kw: typing.Any) -> HTTPConnectionPool: + """ + 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, *_ = parse_url(url) + scheme = scheme or "http" + port = port or port_by_scheme.get(scheme, 80) + if scheme == "https": + return HTTPSConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + else: + return HTTPConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + + +@typing.overload +def _normalize_host(host: None, scheme: str | None) -> None: ... + + +@typing.overload +def _normalize_host(host: str, scheme: str | None) -> str: ... + + +def _normalize_host(host: str | None, scheme: str | None) -> str | None: + """ + 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 and host.startswith("[") and host.endswith("]"): + host = host[1:-1] + return host + + +def _url_from_pool( + pool: HTTPConnectionPool | HTTPSConnectionPool, path: str | None = None +) -> str: + """Returns the URL from a given connection pool. This is mainly used for testing and logging.""" + return Url(scheme=pool.scheme, host=pool.host, port=pool.port, path=path).url + + +def _close_pool_connections(pool: queue.LifoQueue[typing.Any]) -> None: + """Drains a queue of connections and closes each one.""" + try: + while True: + conn = pool.get(block=False) + if conn: + conn.close() + except queue.Empty: + pass # Done. diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__init__.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91a5c8d25d0e85f86a954b82e15fb612097d9fb0 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27a7ae8b4cae8155ddec90c1c5db6ca1e2db4de4 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..053d6e0df08fd1e2111b7d40dd816e887e081043 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__init__.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c0538059ec816395650d45dc4fd416eb73b85f90 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__init__.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +import pip._vendor.urllib3.connection as urllib3_connection + +from ...connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from .connection import EmscriptenHTTPConnection, EmscriptenHTTPSConnection + + +def inject_into_urllib3() -> None: + # override connection classes to use emscripten specific classes + # n.b. mypy complains about the overriding of classes below + # if it isn't ignored + HTTPConnectionPool.ConnectionCls = EmscriptenHTTPConnection + HTTPSConnectionPool.ConnectionCls = EmscriptenHTTPSConnection + urllib3_connection.HTTPConnection = EmscriptenHTTPConnection # type: ignore[misc,assignment] + urllib3_connection.HTTPSConnection = EmscriptenHTTPSConnection # type: ignore[misc,assignment] + urllib3_connection.VerifiedHTTPSConnection = EmscriptenHTTPSConnection # type: ignore[assignment] diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e05f37ab558946c321605002990e572ad91d49a Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff19a5c0dbb5d2feb1c68ff25e29719897111f78 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb5413b9c6fe27ce86b2a3cf51271759278dec9e Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0a27241665999e050b642e4be4c6e819d941d42 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0c002c45673000bfef1da70b785a18dedf7848d Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/connection.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..63f79dd3be803db09671c909f79316c3f65d6916 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/connection.py @@ -0,0 +1,260 @@ +from __future__ import annotations + +import os +import typing + +# use http.client.HTTPException for consistency with non-emscripten +from http.client import HTTPException as HTTPException # noqa: F401 +from http.client import ResponseNotReady + +from ..._base_connection import _TYPE_BODY +from ...connection import HTTPConnection, ProxyConfig, port_by_scheme +from ...exceptions import TimeoutError +from ...response import BaseHTTPResponse +from ...util.connection import _TYPE_SOCKET_OPTIONS +from ...util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from ...util.url import Url +from .fetch import _RequestError, _TimeoutError, send_request, send_streaming_request +from .request import EmscriptenRequest +from .response import EmscriptenHttpResponseWrapper, EmscriptenResponse + +if typing.TYPE_CHECKING: + from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection + + +class EmscriptenHTTPConnection: + default_port: typing.ClassVar[int] = port_by_scheme["http"] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + timeout: None | (float) + + host: str + port: int + blocksize: int + source_address: tuple[str, int] | None + socket_options: _TYPE_SOCKET_OPTIONS | None + + proxy: Url | None + proxy_config: ProxyConfig | None + + is_verified: bool = False + proxy_is_verified: bool | None = None + + response_class: type[BaseHTTPResponse] = EmscriptenHttpResponseWrapper + _response: EmscriptenResponse | None + + def __init__( + self, + host: str, + port: int = 0, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + socket_options: _TYPE_SOCKET_OPTIONS | None = None, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: + self.host = host + self.port = port + self.timeout = timeout if isinstance(timeout, float) else 0.0 + self.scheme = "http" + self._closed = True + self._response = None + # ignore these things because we don't + # have control over that stuff + self.proxy = None + self.proxy_config = None + self.blocksize = blocksize + self.source_address = None + self.socket_options = None + self.is_verified = False + + def set_tunnel( + self, + host: str, + port: int | None = 0, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + pass + + def connect(self) -> None: + pass + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + # We know *at least* botocore is depending on the order of the + # first 3 parameters so to be safe we only mark the later ones + # as keyword-only to ensure we have space to extend. + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: + self._closed = False + if url.startswith("/"): + if self.port is not None: + port = f":{self.port}" + else: + port = "" + # no scheme / host / port included, make a full url + url = f"{self.scheme}://{self.host}{port}{url}" + request = EmscriptenRequest( + url=url, + method=method, + timeout=self.timeout if self.timeout else 0, + decode_content=decode_content, + ) + request.set_body(body) + if headers: + for k, v in headers.items(): + request.set_header(k, v) + self._response = None + try: + if not preload_content: + self._response = send_streaming_request(request) + if self._response is None: + self._response = send_request(request) + except _TimeoutError as e: + raise TimeoutError(e.message) from e + except _RequestError as e: + raise HTTPException(e.message) from e + + def getresponse(self) -> BaseHTTPResponse: + if self._response is not None: + return EmscriptenHttpResponseWrapper( + internal_response=self._response, + url=self._response.request.url, + connection=self, + ) + else: + raise ResponseNotReady() + + def close(self) -> None: + self._closed = True + self._response = None + + @property + def is_closed(self) -> bool: + """Whether the connection either is brand new or has been previously closed. + If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` + properties must be False. + """ + return self._closed + + @property + def is_connected(self) -> bool: + """Whether the connection is actively connected to any origin (proxy or target)""" + return True + + @property + def has_connected_to_proxy(self) -> bool: + """Whether the connection has successfully connected to its proxy. + This returns False if no proxy is in use. Used to determine whether + errors are coming from the proxy layer or from tunnelling to the target origin. + """ + return False + + +class EmscriptenHTTPSConnection(EmscriptenHTTPConnection): + default_port = port_by_scheme["https"] + # all this is basically ignored, as browser handles https + cert_reqs: int | str | None = None + ca_certs: str | None = None + ca_cert_dir: str | None = None + ca_cert_data: None | str | bytes = None + cert_file: str | None + key_file: str | None + key_password: str | None + ssl_context: typing.Any | None + ssl_version: int | str | None = None + ssl_minimum_version: int | None = None + ssl_maximum_version: int | None = None + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None = None + + def __init__( + self, + host: str, + port: int = 0, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: ( + None | _TYPE_SOCKET_OPTIONS + ) = HTTPConnection.default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: typing.Any | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: + super().__init__( + host, + port=port, + timeout=timeout, + source_address=source_address, + blocksize=blocksize, + socket_options=socket_options, + proxy=proxy, + proxy_config=proxy_config, + ) + self.scheme = "https" + + 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 + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + 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 + + self.cert_reqs = None + + # The browser will automatically verify all requests. + # We have no control over that setting. + self.is_verified = True + + def set_cert( + self, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ) -> None: + pass + + +# verify that this class implements BaseHTTP(s) connection correctly +if typing.TYPE_CHECKING: + _supports_http_protocol: BaseHTTPConnection = EmscriptenHTTPConnection("", 0) + _supports_https_protocol: BaseHTTPSConnection = EmscriptenHTTPSConnection("", 0) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/emscripten_fetch_worker.js b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/emscripten_fetch_worker.js new file mode 100644 index 0000000000000000000000000000000000000000..faf141e1fa4113a0c14480d1681ddecb9678ced4 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/emscripten_fetch_worker.js @@ -0,0 +1,110 @@ +let Status = { + SUCCESS_HEADER: -1, + SUCCESS_EOF: -2, + ERROR_TIMEOUT: -3, + ERROR_EXCEPTION: -4, +}; + +let connections = new Map(); +let nextConnectionID = 1; +const encoder = new TextEncoder(); + +self.addEventListener("message", async function (event) { + if (event.data.close) { + let connectionID = event.data.close; + connections.delete(connectionID); + return; + } else if (event.data.getMore) { + let connectionID = event.data.getMore; + let { curOffset, value, reader, intBuffer, byteBuffer } = + connections.get(connectionID); + // if we still have some in buffer, then just send it back straight away + if (!value || curOffset >= value.length) { + // read another buffer if required + try { + let readResponse = await reader.read(); + + if (readResponse.done) { + // read everything - clear connection and return + connections.delete(connectionID); + Atomics.store(intBuffer, 0, Status.SUCCESS_EOF); + Atomics.notify(intBuffer, 0); + // finished reading successfully + // return from event handler + return; + } + curOffset = 0; + connections.get(connectionID).value = readResponse.value; + value = readResponse.value; + } catch (error) { + console.log("Request exception:", error); + let errorBytes = encoder.encode(error.message); + let written = errorBytes.length; + byteBuffer.set(errorBytes); + intBuffer[1] = written; + Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); + Atomics.notify(intBuffer, 0); + } + } + + // send as much buffer as we can + let curLen = value.length - curOffset; + if (curLen > byteBuffer.length) { + curLen = byteBuffer.length; + } + byteBuffer.set(value.subarray(curOffset, curOffset + curLen), 0); + + Atomics.store(intBuffer, 0, curLen); // store current length in bytes + Atomics.notify(intBuffer, 0); + curOffset += curLen; + connections.get(connectionID).curOffset = curOffset; + + return; + } else { + // start fetch + let connectionID = nextConnectionID; + nextConnectionID += 1; + const intBuffer = new Int32Array(event.data.buffer); + const byteBuffer = new Uint8Array(event.data.buffer, 8); + try { + const response = await fetch(event.data.url, event.data.fetchParams); + // return the headers first via textencoder + var headers = []; + for (const pair of response.headers.entries()) { + headers.push([pair[0], pair[1]]); + } + let headerObj = { + headers: headers, + status: response.status, + connectionID, + }; + const headerText = JSON.stringify(headerObj); + let headerBytes = encoder.encode(headerText); + let written = headerBytes.length; + byteBuffer.set(headerBytes); + intBuffer[1] = written; + // make a connection + connections.set(connectionID, { + reader: response.body.getReader(), + intBuffer: intBuffer, + byteBuffer: byteBuffer, + value: undefined, + curOffset: 0, + }); + // set header ready + Atomics.store(intBuffer, 0, Status.SUCCESS_HEADER); + Atomics.notify(intBuffer, 0); + // all fetching after this goes through a new postmessage call with getMore + // this allows for parallel requests + } catch (error) { + console.log("Request exception:", error); + let errorBytes = encoder.encode(error.message); + let written = errorBytes.length; + byteBuffer.set(errorBytes); + intBuffer[1] = written; + Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); + Atomics.notify(intBuffer, 0); + } + } +}); +self.postMessage({ inited: true }); diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/fetch.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/fetch.py new file mode 100644 index 0000000000000000000000000000000000000000..612cfddc4c28d2f0edf47522278fa6d9b7906623 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/fetch.py @@ -0,0 +1,726 @@ +""" +Support for streaming http requests in emscripten. + +A few caveats - + +If your browser (or Node.js) has WebAssembly JavaScript Promise Integration enabled +https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md +*and* you launch pyodide using `pyodide.runPythonAsync`, this will fetch data using the +JavaScript asynchronous fetch api (wrapped via `pyodide.ffi.call_sync`). In this case +timeouts and streaming should just work. + +Otherwise, it uses a combination of XMLHttpRequest and a web-worker for streaming. + +This approach has several caveats: + +Firstly, you can't do streaming http in the main UI thread, because atomics.wait isn't allowed. +Streaming only works if you're running pyodide in a web worker. + +Secondly, this uses an extra web worker and SharedArrayBuffer to do the asynchronous fetch +operation, so it requires that you have crossOriginIsolation enabled, by serving over https +(or from localhost) with the two headers below set: + + Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp + +You can tell if cross origin isolation is successfully enabled by looking at the global crossOriginIsolated variable in +JavaScript console. If it isn't, streaming requests will fallback to XMLHttpRequest, i.e. getting the whole +request into a buffer and then returning it. it shows a warning in the JavaScript console in this case. + +Finally, the webworker which does the streaming fetch is created on initial import, but will only be started once +control is returned to javascript. Call `await wait_for_streaming_ready()` to wait for streaming fetch. + +NB: in this code, there are a lot of JavaScript objects. They are named js_* +to make it clear what type of object they are. +""" + +from __future__ import annotations + +import io +import json +from email.parser import Parser +from importlib.resources import files +from typing import TYPE_CHECKING, Any + +import js # type: ignore[import-not-found] +from pyodide.ffi import ( # type: ignore[import-not-found] + JsArray, + JsException, + JsProxy, + to_js, +) + +if TYPE_CHECKING: + from typing_extensions import Buffer + +from .request import EmscriptenRequest +from .response import EmscriptenResponse + +""" +There are some headers that trigger unintended CORS preflight requests. +See also https://github.com/koenvo/pyodide-http/issues/22 +""" +HEADERS_TO_IGNORE = ("user-agent",) + +SUCCESS_HEADER = -1 +SUCCESS_EOF = -2 +ERROR_TIMEOUT = -3 +ERROR_EXCEPTION = -4 + + +class _RequestError(Exception): + def __init__( + self, + message: str | None = None, + *, + request: EmscriptenRequest | None = None, + response: EmscriptenResponse | None = None, + ): + self.request = request + self.response = response + self.message = message + super().__init__(self.message) + + +class _StreamingError(_RequestError): + pass + + +class _TimeoutError(_RequestError): + pass + + +def _obj_from_dict(dict_val: dict[str, Any]) -> JsProxy: + return to_js(dict_val, dict_converter=js.Object.fromEntries) + + +class _ReadStream(io.RawIOBase): + def __init__( + self, + int_buffer: JsArray, + byte_buffer: JsArray, + timeout: float, + worker: JsProxy, + connection_id: int, + request: EmscriptenRequest, + ): + self.int_buffer = int_buffer + self.byte_buffer = byte_buffer + self.read_pos = 0 + self.read_len = 0 + self.connection_id = connection_id + self.worker = worker + self.timeout = int(1000 * timeout) if timeout > 0 else None + self.is_live = True + self._is_closed = False + self.request: EmscriptenRequest | None = request + + def __del__(self) -> None: + self.close() + + # this is compatible with _base_connection + def is_closed(self) -> bool: + return self._is_closed + + # for compatibility with RawIOBase + @property + def closed(self) -> bool: + return self.is_closed() + + def close(self) -> None: + if self.is_closed(): + return + self.read_len = 0 + self.read_pos = 0 + self.int_buffer = None + self.byte_buffer = None + self._is_closed = True + self.request = None + if self.is_live: + self.worker.postMessage(_obj_from_dict({"close": self.connection_id})) + self.is_live = False + super().close() + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return False + + def seekable(self) -> bool: + return False + + def readinto(self, byte_obj: Buffer) -> int: + if not self.int_buffer: + raise _StreamingError( + "No buffer for stream in _ReadStream.readinto", + request=self.request, + response=None, + ) + if self.read_len == 0: + # wait for the worker to send something + js.Atomics.store(self.int_buffer, 0, ERROR_TIMEOUT) + self.worker.postMessage(_obj_from_dict({"getMore": self.connection_id})) + if ( + js.Atomics.wait(self.int_buffer, 0, ERROR_TIMEOUT, self.timeout) + == "timed-out" + ): + raise _TimeoutError + data_len = self.int_buffer[0] + if data_len > 0: + self.read_len = data_len + self.read_pos = 0 + elif data_len == ERROR_EXCEPTION: + string_len = self.int_buffer[1] + # decode the error string + js_decoder = js.TextDecoder.new() + json_str = js_decoder.decode(self.byte_buffer.slice(0, string_len)) + raise _StreamingError( + f"Exception thrown in fetch: {json_str}", + request=self.request, + response=None, + ) + else: + # EOF, free the buffers and return zero + # and free the request + self.is_live = False + self.close() + return 0 + # copy from int32array to python bytes + ret_length = min(self.read_len, len(memoryview(byte_obj))) + subarray = self.byte_buffer.subarray( + self.read_pos, self.read_pos + ret_length + ).to_py() + memoryview(byte_obj)[0:ret_length] = subarray + self.read_len -= ret_length + self.read_pos += ret_length + return ret_length + + +class _StreamingFetcher: + def __init__(self) -> None: + # make web-worker and data buffer on startup + self.streaming_ready = False + streaming_worker_code = ( + files(__package__) + .joinpath("emscripten_fetch_worker.js") + .read_text(encoding="utf-8") + ) + js_data_blob = js.Blob.new( + to_js([streaming_worker_code], create_pyproxies=False), + _obj_from_dict({"type": "application/javascript"}), + ) + + def promise_resolver(js_resolve_fn: JsProxy, js_reject_fn: JsProxy) -> None: + def onMsg(e: JsProxy) -> None: + self.streaming_ready = True + js_resolve_fn(e) + + def onErr(e: JsProxy) -> None: + js_reject_fn(e) # Defensive: never happens in ci + + self.js_worker.onmessage = onMsg + self.js_worker.onerror = onErr + + js_data_url = js.URL.createObjectURL(js_data_blob) + self.js_worker = js.globalThis.Worker.new(js_data_url) + self.js_worker_ready_promise = js.globalThis.Promise.new(promise_resolver) + + def send(self, request: EmscriptenRequest) -> EmscriptenResponse: + headers = { + k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE + } + + body = request.body + fetch_data = {"headers": headers, "body": to_js(body), "method": request.method} + # start the request off in the worker + timeout = int(1000 * request.timeout) if request.timeout > 0 else None + js_shared_buffer = js.SharedArrayBuffer.new(1048576) + js_int_buffer = js.Int32Array.new(js_shared_buffer) + js_byte_buffer = js.Uint8Array.new(js_shared_buffer, 8) + + js.Atomics.store(js_int_buffer, 0, ERROR_TIMEOUT) + js.Atomics.notify(js_int_buffer, 0) + js_absolute_url = js.URL.new(request.url, js.location).href + self.js_worker.postMessage( + _obj_from_dict( + { + "buffer": js_shared_buffer, + "url": js_absolute_url, + "fetchParams": fetch_data, + } + ) + ) + # wait for the worker to send something + js.Atomics.wait(js_int_buffer, 0, ERROR_TIMEOUT, timeout) + if js_int_buffer[0] == ERROR_TIMEOUT: + raise _TimeoutError( + "Timeout connecting to streaming request", + request=request, + response=None, + ) + elif js_int_buffer[0] == SUCCESS_HEADER: + # got response + # header length is in second int of intBuffer + string_len = js_int_buffer[1] + # decode the rest to a JSON string + js_decoder = js.TextDecoder.new() + # this does a copy (the slice) because decode can't work on shared array + # for some silly reason + json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) + # get it as an object + response_obj = json.loads(json_str) + return EmscriptenResponse( + request=request, + status_code=response_obj["status"], + headers=response_obj["headers"], + body=_ReadStream( + js_int_buffer, + js_byte_buffer, + request.timeout, + self.js_worker, + response_obj["connectionID"], + request, + ), + ) + elif js_int_buffer[0] == ERROR_EXCEPTION: + string_len = js_int_buffer[1] + # decode the error string + js_decoder = js.TextDecoder.new() + json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) + raise _StreamingError( + f"Exception thrown in fetch: {json_str}", request=request, response=None + ) + else: + raise _StreamingError( + f"Unknown status from worker in fetch: {js_int_buffer[0]}", + request=request, + response=None, + ) + + +class _JSPIReadStream(io.RawIOBase): + """ + A read stream that uses pyodide.ffi.run_sync to read from a JavaScript fetch + response. This requires support for WebAssembly JavaScript Promise Integration + in the containing browser, and for pyodide to be launched via runPythonAsync. + + :param js_read_stream: + The JavaScript stream reader + + :param timeout: + Timeout in seconds + + :param request: + The request we're handling + + :param response: + The response this stream relates to + + :param js_abort_controller: + A JavaScript AbortController object, used for timeouts + """ + + def __init__( + self, + js_read_stream: Any, + timeout: float, + request: EmscriptenRequest, + response: EmscriptenResponse, + js_abort_controller: Any, # JavaScript AbortController for timeouts + ): + self.js_read_stream = js_read_stream + self.timeout = timeout + self._is_closed = False + self._is_done = False + self.request: EmscriptenRequest | None = request + self.response: EmscriptenResponse | None = response + self.current_buffer = None + self.current_buffer_pos = 0 + self.js_abort_controller = js_abort_controller + + def __del__(self) -> None: + self.close() + + # this is compatible with _base_connection + def is_closed(self) -> bool: + return self._is_closed + + # for compatibility with RawIOBase + @property + def closed(self) -> bool: + return self.is_closed() + + def close(self) -> None: + if self.is_closed(): + return + self.read_len = 0 + self.read_pos = 0 + self.js_read_stream.cancel() + self.js_read_stream = None + self._is_closed = True + self._is_done = True + self.request = None + self.response = None + super().close() + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return False + + def seekable(self) -> bool: + return False + + def _get_next_buffer(self) -> bool: + result_js = _run_sync_with_timeout( + self.js_read_stream.read(), + self.timeout, + self.js_abort_controller, + request=self.request, + response=self.response, + ) + if result_js.done: + self._is_done = True + return False + else: + self.current_buffer = result_js.value.to_py() + self.current_buffer_pos = 0 + return True + + def readinto(self, byte_obj: Buffer) -> int: + if self.current_buffer is None: + if not self._get_next_buffer() or self.current_buffer is None: + self.close() + return 0 + ret_length = min( + len(byte_obj), len(self.current_buffer) - self.current_buffer_pos + ) + byte_obj[0:ret_length] = self.current_buffer[ + self.current_buffer_pos : self.current_buffer_pos + ret_length + ] + self.current_buffer_pos += ret_length + if self.current_buffer_pos == len(self.current_buffer): + self.current_buffer = None + return ret_length + + +# check if we are in a worker or not +def is_in_browser_main_thread() -> bool: + return hasattr(js, "window") and hasattr(js, "self") and js.self == js.window + + +def is_cross_origin_isolated() -> bool: + return hasattr(js, "crossOriginIsolated") and js.crossOriginIsolated + + +def is_in_node() -> bool: + return ( + hasattr(js, "process") + and hasattr(js.process, "release") + and hasattr(js.process.release, "name") + and js.process.release.name == "node" + ) + + +def is_worker_available() -> bool: + return hasattr(js, "Worker") and hasattr(js, "Blob") + + +_fetcher: _StreamingFetcher | None = None + +if is_worker_available() and ( + (is_cross_origin_isolated() and not is_in_browser_main_thread()) + and (not is_in_node()) +): + _fetcher = _StreamingFetcher() +else: + _fetcher = None + + +NODE_JSPI_ERROR = ( + "urllib3 only works in Node.js with pyodide.runPythonAsync" + " and requires the flag --experimental-wasm-stack-switching in " + " versions of node <24." +) + + +def send_streaming_request(request: EmscriptenRequest) -> EmscriptenResponse | None: + if has_jspi(): + return send_jspi_request(request, True) + elif is_in_node(): + raise _RequestError( + message=NODE_JSPI_ERROR, + request=request, + response=None, + ) + + if _fetcher and streaming_ready(): + return _fetcher.send(request) + else: + _show_streaming_warning() + return None + + +_SHOWN_TIMEOUT_WARNING = False + + +def _show_timeout_warning() -> None: + global _SHOWN_TIMEOUT_WARNING + if not _SHOWN_TIMEOUT_WARNING: + _SHOWN_TIMEOUT_WARNING = True + message = "Warning: Timeout is not available on main browser thread" + js.console.warn(message) + + +_SHOWN_STREAMING_WARNING = False + + +def _show_streaming_warning() -> None: + global _SHOWN_STREAMING_WARNING + if not _SHOWN_STREAMING_WARNING: + _SHOWN_STREAMING_WARNING = True + message = "Can't stream HTTP requests because: \n" + if not is_cross_origin_isolated(): + message += " Page is not cross-origin isolated\n" + if is_in_browser_main_thread(): + message += " Python is running in main browser thread\n" + if not is_worker_available(): + message += " Worker or Blob classes are not available in this environment." # Defensive: this is always False in browsers that we test in + if streaming_ready() is False: + message += """ Streaming fetch worker isn't ready. If you want to be sure that streaming fetch +is working, you need to call: 'await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()`""" + from js import console + + console.warn(message) + + +def send_request(request: EmscriptenRequest) -> EmscriptenResponse: + if has_jspi(): + return send_jspi_request(request, False) + elif is_in_node(): + raise _RequestError( + message=NODE_JSPI_ERROR, + request=request, + response=None, + ) + try: + js_xhr = js.XMLHttpRequest.new() + + if not is_in_browser_main_thread(): + js_xhr.responseType = "arraybuffer" + if request.timeout: + js_xhr.timeout = int(request.timeout * 1000) + else: + js_xhr.overrideMimeType("text/plain; charset=ISO-8859-15") + if request.timeout: + # timeout isn't available on the main thread - show a warning in console + # if it is set + _show_timeout_warning() + + js_xhr.open(request.method, request.url, False) + for name, value in request.headers.items(): + if name.lower() not in HEADERS_TO_IGNORE: + js_xhr.setRequestHeader(name, value) + + js_xhr.send(to_js(request.body)) + + headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders())) + + if not is_in_browser_main_thread(): + body = js_xhr.response.to_py().tobytes() + else: + body = js_xhr.response.encode("ISO-8859-15") + return EmscriptenResponse( + status_code=js_xhr.status, headers=headers, body=body, request=request + ) + except JsException as err: + if err.name == "TimeoutError": + raise _TimeoutError(err.message, request=request) + elif err.name == "NetworkError": + raise _RequestError(err.message, request=request) + else: + # general http error + raise _RequestError(err.message, request=request) + + +def send_jspi_request( + request: EmscriptenRequest, streaming: bool +) -> EmscriptenResponse: + """ + Send a request using WebAssembly JavaScript Promise Integration + to wrap the asynchronous JavaScript fetch api (experimental). + + :param request: + Request to send + + :param streaming: + Whether to stream the response + + :return: The response object + :rtype: EmscriptenResponse + """ + timeout = request.timeout + js_abort_controller = js.AbortController.new() + headers = {k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE} + req_body = request.body + fetch_data = { + "headers": headers, + "body": to_js(req_body), + "method": request.method, + "signal": js_abort_controller.signal, + } + # Node.js returns the whole response (unlike opaqueredirect in browsers), + # so urllib3 can set `redirect: manual` to control redirects itself. + # https://stackoverflow.com/a/78524615 + if _is_node_js(): + fetch_data["redirect"] = "manual" + # Call JavaScript fetch (async api, returns a promise) + fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data)) + # Now suspend WebAssembly until we resolve that promise + # or time out. + response_js = _run_sync_with_timeout( + fetcher_promise_js, + timeout, + js_abort_controller, + request=request, + response=None, + ) + headers = {} + header_iter = response_js.headers.entries() + while True: + iter_value_js = header_iter.next() + if getattr(iter_value_js, "done", False): + break + else: + headers[str(iter_value_js.value[0])] = str(iter_value_js.value[1]) + status_code = response_js.status + body: bytes | io.RawIOBase = b"" + + response = EmscriptenResponse( + status_code=status_code, headers=headers, body=b"", request=request + ) + if streaming: + # get via inputstream + if response_js.body is not None: + # get a reader from the fetch response + body_stream_js = response_js.body.getReader() + body = _JSPIReadStream( + body_stream_js, timeout, request, response, js_abort_controller + ) + else: + # get directly via arraybuffer + # n.b. this is another async JavaScript call. + body = _run_sync_with_timeout( + response_js.arrayBuffer(), + timeout, + js_abort_controller, + request=request, + response=response, + ).to_py() + response.body = body + return response + + +def _run_sync_with_timeout( + promise: Any, + timeout: float, + js_abort_controller: Any, + request: EmscriptenRequest | None, + response: EmscriptenResponse | None, +) -> Any: + """ + Await a JavaScript promise synchronously with a timeout which is implemented + via the AbortController + + :param promise: + Javascript promise to await + + :param timeout: + Timeout in seconds + + :param js_abort_controller: + A JavaScript AbortController object, used on timeout + + :param request: + The request being handled + + :param response: + The response being handled (if it exists yet) + + :raises _TimeoutError: If the request times out + :raises _RequestError: If the request raises a JavaScript exception + + :return: The result of awaiting the promise. + """ + timer_id = None + if timeout > 0: + timer_id = js.setTimeout( + js_abort_controller.abort.bind(js_abort_controller), int(timeout * 1000) + ) + try: + from pyodide.ffi import run_sync + + # run_sync here uses WebAssembly JavaScript Promise Integration to + # suspend python until the JavaScript promise resolves. + return run_sync(promise) + except JsException as err: + if err.name == "AbortError": + raise _TimeoutError( + message="Request timed out", request=request, response=response + ) + else: + raise _RequestError(message=err.message, request=request, response=response) + finally: + if timer_id is not None: + js.clearTimeout(timer_id) + + +def has_jspi() -> bool: + """ + Return true if jspi can be used. + + This requires both browser support and also WebAssembly + to be in the correct state - i.e. that the javascript + call into python was async not sync. + + :return: True if jspi can be used. + :rtype: bool + """ + try: + from pyodide.ffi import can_run_sync, run_sync # noqa: F401 + + return bool(can_run_sync()) + except ImportError: + return False + + +def _is_node_js() -> bool: + """ + Check if we are in Node.js. + + :return: True if we are in Node.js. + :rtype: bool + """ + return ( + hasattr(js, "process") + and hasattr(js.process, "release") + # According to the Node.js documentation, the release name is always "node". + and js.process.release.name == "node" + ) + + +def streaming_ready() -> bool | None: + if _fetcher: + return _fetcher.streaming_ready + else: + return None # no fetcher, return None to signify that + + +async def wait_for_streaming_ready() -> bool: + if _fetcher: + await _fetcher.js_worker_ready_promise + return True + else: + return False diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/request.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/request.py new file mode 100644 index 0000000000000000000000000000000000000000..e692e692bd0d38f6a0677992a6993fc68050dff3 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/request.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from ..._base_connection import _TYPE_BODY + + +@dataclass +class EmscriptenRequest: + method: str + url: str + params: dict[str, str] | None = None + body: _TYPE_BODY | None = None + headers: dict[str, str] = field(default_factory=dict) + timeout: float = 0 + decode_content: bool = True + + def set_header(self, name: str, value: str) -> None: + self.headers[name.capitalize()] = value + + def set_body(self, body: _TYPE_BODY | None) -> None: + self.body = body diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/response.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/response.py new file mode 100644 index 0000000000000000000000000000000000000000..ec1e1dbe83722c5fb16290a852859252ae616826 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/emscripten/response.py @@ -0,0 +1,281 @@ +from __future__ import annotations + +import json as _json +import logging +import typing +from contextlib import contextmanager +from dataclasses import dataclass +from http.client import HTTPException as HTTPException +from io import BytesIO, IOBase + +from ...exceptions import InvalidHeader, TimeoutError +from ...response import BaseHTTPResponse +from ...util.retry import Retry +from .request import EmscriptenRequest + +if typing.TYPE_CHECKING: + from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection + +log = logging.getLogger(__name__) + + +@dataclass +class EmscriptenResponse: + status_code: int + headers: dict[str, str] + body: IOBase | bytes + request: EmscriptenRequest + + +class EmscriptenHttpResponseWrapper(BaseHTTPResponse): + def __init__( + self, + internal_response: EmscriptenResponse, + url: str | None = None, + connection: BaseHTTPConnection | BaseHTTPSConnection | None = None, + ): + self._pool = None # set by pool class + self._body = None + self._uncached_read_occurred = False + self._response = internal_response + self._url = url + self._connection = connection + self._closed = False + super().__init__( + headers=internal_response.headers, + status=internal_response.status_code, + request_url=url, + version=0, + version_string="HTTP/?", + reason="", + decode_content=True, + ) + self.length_remaining = self._init_length(self._response.request.method) + self.length_is_certain = False + + @property + def url(self) -> str | None: + return self._url + + @url.setter + def url(self, url: str | None) -> None: + self._url = url + + @property + def connection(self) -> BaseHTTPConnection | BaseHTTPSConnection | None: + return self._connection + + @property + def retries(self) -> Retry | None: + return self._retries + + @retries.setter + def retries(self, retries: Retry | None) -> None: + # Override the request_url if retries has a redirect location. + self._retries = retries + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + 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. + """ + while True: + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + else: + break + + def _init_length(self, request_method: str | None) -> int | None: + length: int | None + content_length: str | None = self.headers.get("content-length") + + if content_length is not 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 = {int(val) for val in content_length.split(",")} + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % content_length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + else: # if content_length is None + length = None + + # Check for responses that shouldn't include a body + if ( + self.status in (204, 304) + or 100 <= self.status < 200 + or request_method == "HEAD" + ): + length = 0 + + return length + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, # ignored because browser decodes always + cache_content: bool = False, + ) -> bytes: + if ( + self._closed + or self._response is None + or (isinstance(self._response.body, IOBase) and self._response.body.closed) + ): + return b"" + + with self._error_catcher(): + # body has been preloaded as a string by XmlHttpRequest + if not isinstance(self._response.body, IOBase): + self.length_remaining = len(self._response.body) + self.length_is_certain = True + # wrap body in IOStream + self._response.body = BytesIO(self._response.body) + if amt is not None and amt >= 0: + # don't cache partial content + cache_content = False + data = self._response.body.read(amt) + self._uncached_read_occurred = True + else: # read all we can (and cache it) + data = self._response.body.read() + if cache_content and not self._uncached_read_occurred: + self._body = data + else: + self._uncached_read_occurred = True + if self.length_remaining is not None: + self.length_remaining = max(self.length_remaining - len(data), 0) + if len(data) == 0 or ( + self.length_is_certain and self.length_remaining == 0 + ): + # definitely finished reading, close response stream + self._response.body.close() + return typing.cast(bytes, data) + + def read_chunked( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> typing.Generator[bytes]: + # chunked is handled by browser + while True: + bytes = self.read(amt, decode_content) + if not bytes: + break + yield bytes + + def release_conn(self) -> None: + if not self._pool or not self._connection: + return None + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self) -> None: + self.close() + + @property + def data(self) -> bytes: + if self._body: + return self._body + else: + return self.read(cache_content=True) + + def json(self) -> typing.Any: + """ + Deserializes the body of the HTTP response as a Python object. + + The body of the HTTP response must be encoded using UTF-8, as per + `RFC 8529 Section 8.1 `_. + + To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to + your custom decoder instead. + + If the body of the HTTP response is not decodable to UTF-8, a + `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a + valid JSON document, a `json.JSONDecodeError` will be raised. + + Read more :ref:`here `. + + :returns: The body of the HTTP response as a Python object. + """ + data = self.data.decode("utf-8") + return _json.loads(data) + + def close(self) -> None: + if not self._closed: + if isinstance(self._response.body, IOBase): + self._response.body.close() + if self._connection: + self._connection.close() + self._connection = None + self._closed = True + + @contextmanager + def _error_catcher(self) -> typing.Generator[None]: + """ + Catch Emscripten specific exceptions thrown by fetch.py, + 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. + """ + from .fetch import _RequestError, _TimeoutError # avoid circular import + + clean_exit = False + + try: + yield + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + except _TimeoutError as e: + raise TimeoutError(str(e)) + except _RequestError as e: + raise HTTPException(str(e)) + 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 + if ( + isinstance(self._response.body, IOBase) + and not self._response.body.closed + ): + self._response.body.close() + # release the connection back to the pool + self.release_conn() + else: + # If we have read everything from the response stream, + # return the connection back to the pool. + if ( + isinstance(self._response.body, IOBase) + and self._response.body.closed + ): + self.release_conn() diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000000000000000000000000000000000000..f29d2e645d8ce660212f7127b7d1f4ba105bddc5 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py @@ -0,0 +1,563 @@ +""" +Module for using pyOpenSSL as a TLS backend. This module was relevant before +the standard library ``ssl`` module supported SNI, but now that we've dropped +support for Python 2.7 all relevant Python versions support SNI so +**this module is no longer recommended**. + +This needs the following packages installed: + +* `pyOpenSSL`_ (tested with 19.0.0) +* `cryptography`_ (minimum 2.3, from pyopenssl) +* `idna`_ (minimum 2.1, from cryptography) + +However, pyOpenSSL depends on cryptography, 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 pip._vendor.urllib3.contrib.pyopenssl as pyopenssl + pyopenssl.inject_into_urllib3() + except ImportError: + pass + +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna +""" + +from __future__ import annotations + +import OpenSSL.SSL # type: ignore[import-not-found] +from cryptography import x509 + +try: + from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined] +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): # type: ignore[no-redef] + pass + + +import logging +import ssl +import typing +from io import BytesIO +from socket import socket as socket_cls + +from .. import util + +if typing.TYPE_CHECKING: + from OpenSSL.crypto import X509 # type: ignore[import-not-found] + + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions: dict[int, int] = { + util.ssl_.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] + util.ssl_.PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_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 = {v: k for k, v in _stdlib_to_openssl_verify.items()} + +# The SSLvX values are the most likely to be missing in the future +# but we check them all just to be sure. +_OP_NO_SSLv2_OR_SSLv3: int = getattr(OpenSSL.SSL, "OP_NO_SSLv2", 0) | getattr( + OpenSSL.SSL, "OP_NO_SSLv3", 0 +) +_OP_NO_TLSv1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1", 0) +_OP_NO_TLSv1_1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_1", 0) +_OP_NO_TLSv1_2: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_2", 0) +_OP_NO_TLSv1_3: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_3", 0) + +_openssl_to_ssl_minimum_version: dict[int, int] = { + ssl.TLSVersion.MINIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.TLSv1: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1, + ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1, + ssl.TLSVersion.TLSv1_3: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 + ), + ssl.TLSVersion.MAXIMUM_SUPPORTED: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 + ), +} +_openssl_to_ssl_maximum_version: dict[int, int] = { + ssl.TLSVersion.MINIMUM_SUPPORTED: ( + _OP_NO_SSLv2_OR_SSLv3 + | _OP_NO_TLSv1 + | _OP_NO_TLSv1_1 + | _OP_NO_TLSv1_2 + | _OP_NO_TLSv1_3 + ), + ssl.TLSVersion.TLSv1: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3 + ), + ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3, + ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_3, + ssl.TLSVersion.TLSv1_3: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.MAXIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, +} + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3() -> None: + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." + + _validate_dependencies_met() + + util.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.ssl_.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3() -> None: + "Undo monkey-patching by :func:`inject_into_urllib3`." + + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met() -> None: + """ + 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: str) -> str | None: + """ + 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: str) -> bytes | None: + """ + 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. + """ + from pip._vendor import idna + + try: + for prefix in ["*.", "."]: + 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 + + encoded_name = idna_encode(name) + if encoded_name is None: + return None + return encoded_name.decode("utf-8") + + +def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]: + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + cert = peer_cert.to_cryptography() + + # 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: + """API-compatibility wrapper for Python OpenSSL's Connection-class.""" + + def __init__( + self, + connection: OpenSSL.SSL.Connection, + socket: socket_cls, + suppress_ragged_eofs: bool = True, + ) -> None: + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._io_refs = 0 + self._closed = False + + def fileno(self) -> int: + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self) -> None: + if self._io_refs > 0: + self._io_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args: typing.Any, **kwargs: typing.Any) -> bytes: + 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 OSError(e.args[0], str(e)) from e + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b"" + else: + raise + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise TimeoutError("The read operation timed out") from e + else: + return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"read error: {e!r}") from e + else: + return data # type: ignore[no-any-return] + + def recv_into(self, *args: typing.Any, **kwargs: typing.Any) -> int: + try: + return self.connection.recv_into(*args, **kwargs) # type: ignore[no-any-return] + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return 0 + else: + raise OSError(e.args[0], str(e)) from e + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise TimeoutError("The read operation timed out") from e + else: + return self.recv_into(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"read error: {e!r}") from e + + def settimeout(self, timeout: float) -> None: + return self.socket.settimeout(timeout) + + def _send_until_done(self, data: bytes) -> int: + while True: + try: + return self.connection.send(data) # type: ignore[no-any-return] + except OpenSSL.SSL.WantWriteError as e: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise TimeoutError() from e + continue + except OpenSSL.SSL.SysCallError as e: + raise OSError(e.args[0], str(e)) from e + + def sendall(self, data: bytes) -> None: + 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, how: int) -> None: + try: + self.connection.shutdown() + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"shutdown error: {e!r}") from e + + def close(self) -> None: + self._closed = True + if self._io_refs <= 0: + self._real_close() + + def _real_close(self) -> None: + try: + return self.connection.close() # type: ignore[no-any-return] + except OpenSSL.SSL.Error: + return + + def getpeercert( + self, binary_form: bool = False + ) -> dict[str, list[typing.Any]] | None: + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 # type: ignore[no-any-return] + + if binary_form: + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return] + + return { + "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item] + "subjectAltName": get_subj_alt_name(x509), + } + + def version(self) -> str: + return self.connection.get_protocol_version_name() # type: ignore[no-any-return] + + def selected_alpn_protocol(self) -> str | None: + alpn_proto = self.connection.get_alpn_proto_negotiated() + return alpn_proto.decode() if alpn_proto else None + + +WrappedSocket.makefile = socket_cls.makefile # type: ignore[attr-defined] + + +class PyOpenSSLContext: + """ + 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: int) -> None: + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + self._minimum_version: int = ssl.TLSVersion.MINIMUM_SUPPORTED + self._maximum_version: int = ssl.TLSVersion.MAXIMUM_SUPPORTED + self._verify_flags: int = ssl.VERIFY_X509_TRUSTED_FIRST + + @property + def options(self) -> int: + return self._options + + @options.setter + def options(self, value: int) -> None: + self._options = value + self._set_ctx_options() + + @property + def verify_flags(self) -> int: + return self._verify_flags + + @verify_flags.setter + def verify_flags(self, value: int) -> None: + self._verify_flags = value + self._ctx.get_cert_store().set_flags(self._verify_flags) + + @property + def verify_mode(self) -> int: + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value: ssl.VerifyMode) -> None: + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) + + def set_default_verify_paths(self) -> None: + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers: bytes | str) -> None: + if isinstance(ciphers, str): + ciphers = ciphers.encode("utf-8") + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations( + self, + cafile: str | None = None, + capath: str | None = None, + cadata: bytes | None = None, + ) -> None: + if cafile is not None: + cafile = cafile.encode("utf-8") # type: ignore[assignment] + if capath is not None: + capath = capath.encode("utf-8") # type: ignore[assignment] + 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(f"unable to load trusted certificates: {e!r}") from e + + def load_cert_chain( + self, + certfile: str, + keyfile: str | None = None, + password: str | None = None, + ) -> None: + try: + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, bytes): + password = password.encode("utf-8") # type: ignore[assignment] + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e + + def set_alpn_protocols(self, protocols: list[bytes | str]) -> None: + protocols = [util.util.to_bytes(p, "ascii") for p in protocols] + return self._ctx.set_alpn_protos(protocols) # type: ignore[no-any-return] + + def wrap_socket( + self, + sock: socket_cls, + server_side: bool = False, + do_handshake_on_connect: bool = True, + suppress_ragged_eofs: bool = True, + server_hostname: bytes | str | None = None, + ) -> WrappedSocket: + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + # If server_hostname is an IP, don't use it for SNI, per RFC6066 Section 3 + if server_hostname and not util.ssl_.is_ipaddress(server_hostname): + if isinstance(server_hostname, str): + server_hostname = server_hostname.encode("utf-8") + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(sock, sock.gettimeout()): + raise TimeoutError("select timed out") from e + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"bad handshake: {e!r}") from e + break + + return WrappedSocket(cnx, sock) + + def _set_ctx_options(self) -> None: + self._ctx.set_options( + self._options + | _openssl_to_ssl_minimum_version[self._minimum_version] + | _openssl_to_ssl_maximum_version[self._maximum_version] + ) + + @property + def minimum_version(self) -> int: + return self._minimum_version + + @minimum_version.setter + def minimum_version(self, minimum_version: int) -> None: + self._minimum_version = minimum_version + self._set_ctx_options() + + @property + def maximum_version(self) -> int: + return self._maximum_version + + @maximum_version.setter + def maximum_version(self, maximum_version: int) -> None: + self._maximum_version = maximum_version + self._set_ctx_options() + + +def _verify_callback( + cnx: OpenSSL.SSL.Connection, + x509: X509, + err_no: int, + err_depth: int, + return_code: int, +) -> bool: + return err_no == 0 diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/socks.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/socks.py new file mode 100644 index 0000000000000000000000000000000000000000..d37da8fc2049a7b3f7e18d2f64e79c205cfe3d3e --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/socks.py @@ -0,0 +1,228 @@ +""" +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 annotations + +try: + import socks # type: ignore[import-untyped] +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/latest/advanced-usage.html#socks-proxies" + ), + DependencyWarning, + ) + raise + +import typing +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 # type: ignore[assignment] + + +class _TYPE_SOCKS_OPTIONS(typing.TypedDict): + socks_version: int + proxy_host: str | None + proxy_port: str | None + username: str | None + password: str | None + rdns: bool + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + + def __init__( + self, + _socks_options: _TYPE_SOCKS_OPTIONS, + *args: typing.Any, + **kwargs: typing.Any, + ) -> None: + self._socks_options = _socks_options + super().__init__(*args, **kwargs) + + def _new_conn(self) -> socks.socksocket: + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw: dict[str, typing.Any] = {} + 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 as e: + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + + 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, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + else: + # Adding `from e` messes with coverage somehow, so it's omitted. + # See #2386. + raise NewConnectionError( + self, f"Failed to establish a new connection: {error}" + ) + else: # Defensive: see https://github.com/urllib3/urllib3/pull/3728#pullrequestreview-3816302703 + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + except OSError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from 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: str, + username: str | None = None, + password: str | None = None, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + **connection_pool_kw: typing.Any, + ): + 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(f"Unable to determine SOCKS version from {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().__init__(num_pools, headers, **connection_pool_kw) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/exceptions.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..3d7e9b93d307ffe7d54868122227a165cc98383c --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/exceptions.py @@ -0,0 +1,335 @@ +from __future__ import annotations + +import socket +import typing +import warnings +from email.errors import MessageDefect +from http.client import IncompleteRead as httplib_IncompleteRead + +if typing.TYPE_CHECKING: + from .connection import HTTPConnection + from .connectionpool import ConnectionPool + from .response import HTTPResponse + from .util.retry import Retry + +# Base Exceptions + + +class HTTPError(Exception): + """Base exception used by this module.""" + + +class HTTPWarning(Warning): + """Base warning used by this module.""" + + +_TYPE_REDUCE_RESULT = tuple[typing.Callable[..., object], tuple[object, ...]] + + +class PoolError(HTTPError): + """Base exception for errors caused within a pool.""" + + def __init__(self, pool: ConnectionPool, message: str) -> None: + self.pool = pool + self._message = message + super().__init__(f"{pool}: {message}") + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self._message) + + +class RequestError(PoolError): + """Base exception for PoolErrors that have associated URLs.""" + + def __init__(self, pool: ConnectionPool, url: str | None, message: str) -> None: + self.url = url + super().__init__(pool, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self.url, self._message) + + +class SSLError(HTTPError): + """Raised when SSL certificate fails in an HTTPS connection.""" + + +class ProxyError(HTTPError): + """Raised when the connection to a proxy fails.""" + + # The original error is also available as __cause__. + original_error: Exception + + def __init__(self, message: str, error: Exception) -> None: + super().__init__(message, error) + self.original_error = error + + +class DecodeError(HTTPError): + """Raised when automatic decoding based on Content-Type fails.""" + + +class ProtocolError(HTTPError): + """Raised when something unexpected happens mid-request/response.""" + + +#: 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 str url: The requested Url + :param reason: The underlying error + :type reason: :class:`Exception` + + """ + + def __init__( + self, pool: ConnectionPool, url: str | None, reason: Exception | None = None + ) -> None: + self.reason = reason + + message = f"Max retries exceeded with url: {url} (Caused by {reason!r})" + + super().__init__(pool, url, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self.url, self.reason) + + +class HostChangedError(RequestError): + """Raised when an existing pool gets a request for a foreign host.""" + + def __init__( + self, pool: ConnectionPool, url: str, retries: Retry | int = 3 + ) -> None: + message = f"Tried to open a foreign host with url: {url}" + super().__init__(pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """Raised when passing an invalid state to a timeout""" + + +class TimeoutError(HTTPError): + """Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + ` and :exc:`ConnectTimeoutErrors `. + """ + + +class ReadTimeoutError(TimeoutError, RequestError): + """Raised when a socket timeout occurs while receiving data from a server""" + + +# 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""" + + +class NewConnectionError(ConnectTimeoutError, HTTPError): + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + + def __init__(self, conn: HTTPConnection, message: str) -> None: + self.conn = conn + self._message = message + super().__init__(f"{conn}: {message}") + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self._message) + + @property + def pool(self) -> HTTPConnection: + warnings.warn( + "The 'pool' property is deprecated and will be removed " + "in urllib3 v3.0. Use 'conn' instead.", + FutureWarning, + stacklevel=2, + ) + + return self.conn + + +class NameResolutionError(NewConnectionError): + """Raised when host name resolution fails.""" + + def __init__(self, host: str, conn: HTTPConnection, reason: socket.gaierror): + message = f"Failed to resolve '{host}' ({reason})" + self._host = host + self._reason = reason + super().__init__(conn, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (self._host, None, self._reason) + + +class EmptyPoolError(PoolError): + """Raised when a pool runs out of connections and no more are allowed.""" + + +class FullPoolError(PoolError): + """Raised when we try to add a connection to a full pool in blocking mode.""" + + +class ClosedPoolError(PoolError): + """Raised when a request enters a pool after the pool has been closed.""" + + +class LocationValueError(ValueError, HTTPError): + """Raised when there is something wrong with a given URL input.""" + + +class LocationParseError(LocationValueError): + """Raised when get_host or similar fails to parse the URL input.""" + + def __init__(self, location: str) -> None: + message = f"Failed to parse: {location}" + super().__init__(message) + + self.location = location + + +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme: str): + message = f"Not supported URL scheme {scheme}" + super().__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""" + + +class InsecureRequestWarning(SecurityWarning): + """Warned when making an unverified HTTPS request.""" + + +class NotOpenSSLWarning(SecurityWarning): + """Warned when using unsupported SSL library""" + + +class SystemTimeWarning(SecurityWarning): + """Warned when system time is suspected to be wrong""" + + +class InsecurePlatformWarning(SecurityWarning): + """Warned when certain TLS/SSL configuration is not available on a platform.""" + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + + +class ResponseNotChunked(ProtocolError, ValueError): + """Response needs to be chunked in order to read it as chunks.""" + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). + """ + + +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. + """ + + partial: int # type: ignore[assignment] + expected: int + + def __init__(self, partial: int, expected: int) -> None: + self.partial = partial + self.expected = expected + + def __repr__(self) -> str: + 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: HTTPResponse, length: bytes) -> None: + self.partial: int = response.tell() # type: ignore[assignment] + self.expected: int | None = response.length_remaining + self.response = response + self.length = length + + def __repr__(self) -> str: + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) + + +class InvalidHeader(HTTPError): + """The header provided was somehow invalid.""" + + +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: str | None) -> None: + # '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 = f"Proxy URL had unsupported scheme {scheme}, should use http:// or https://" + super().__init__(message) + + +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + +class HeaderParsingError(HTTPError): + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + + def __init__( + self, defects: list[MessageDefect], unparsed_data: bytes | str | None + ) -> None: + message = f"{defects or 'Unknown'}, unparsed data: {unparsed_data!r}" + super().__init__(message) + + +class UnrewindableBodyError(HTTPError): + """urllib3 encountered an error when trying to rewind a body""" diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/fields.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/fields.py new file mode 100644 index 0000000000000000000000000000000000000000..fe68e177324bc67f145091e464556a69b89bcda7 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/fields.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import email.utils +import mimetypes +import typing + +_TYPE_FIELD_VALUE = typing.Union[str, bytes] +_TYPE_FIELD_VALUE_TUPLE = typing.Union[ + _TYPE_FIELD_VALUE, + tuple[str, _TYPE_FIELD_VALUE], + tuple[str, _TYPE_FIELD_VALUE, str], +] + + +def guess_content_type( + filename: str | None, default: str = "application/octet-stream" +) -> str: + """ + 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: str, value: _TYPE_FIELD_VALUE) -> str: + """ + 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``. + :returns: + An RFC-2231-formatted unicode string. + + .. deprecated:: 2.0.0 + Will be removed in urllib3 v3.0. This is not valid for + ``multipart/form-data`` header parameters. + """ + import warnings + + warnings.warn( + "'format_header_param_rfc2231' is insecure, deprecated and will be " + "removed in urllib3 v3.0. This is not valid for " + "multipart/form-data header parameters.", + FutureWarning, + stacklevel=2, + ) + + if isinstance(value, bytes): + value = value.decode("utf-8") + + if not any(ch in value for ch in '"\\\r\n'): + result = f'{name}="{value}"' + try: + result.encode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + + value = email.utils.encode_rfc2231(value, "utf-8") + value = f"{name}*={value}" + + return value + + +def format_multipart_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Format and quote a single multipart header parameter. + + This follows the `WHATWG HTML Standard`_ as of 2021/06/10, matching + the behavior of current browser and curl versions. Values are + assumed to be UTF-8. The ``\\n``, ``\\r``, and ``"`` characters are + percent encoded. + + .. _WHATWG HTML Standard: + https://html.spec.whatwg.org/multipage/ + form-control-infrastructure.html#multipart-form-data + + :param name: + The name of the parameter, an ASCII-only ``str``. + :param value: + The value of the parameter, a ``str`` or UTF-8 encoded + ``bytes``. + :returns: + A string ``name="value"`` with the escaped value. + + .. versionchanged:: 2.0.0 + Matches the WHATWG HTML Standard as of 2021/06/10. Control + characters are no longer percent encoded. + + .. versionchanged:: 2.0.0 + Renamed from ``format_header_param_html5`` and + ``format_header_param``. The old names will be removed in + urllib3 v3.0. + """ + if isinstance(value, bytes): + value = value.decode("utf-8") + + # percent encode \n \r " + value = value.translate({10: "%0A", 13: "%0D", 34: "%22"}) + return f'{name}="{value}"' + + +def format_header_param_html5(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + .. deprecated:: 2.0.0 + Renamed to :func:`format_multipart_header_param`. Will be + removed in urllib3 v3.0. + """ + import warnings + + warnings.warn( + "'format_header_param_html5' has been renamed to " + "'format_multipart_header_param'. The old name will be " + "removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + return format_multipart_header_param(name, value) + + +def format_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + .. deprecated:: 2.0.0 + Renamed to :func:`format_multipart_header_param`. Will be + removed in urllib3 v3.0. + """ + import warnings + + warnings.warn( + "'format_header_param' has been renamed to " + "'format_multipart_header_param'. The old name will be " + "removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + return format_multipart_header_param(name, value) + + +class RequestField: + """ + 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. + + .. versionchanged:: 2.0.0 + The ``header_formatter`` parameter is deprecated and will + be removed in urllib3 v3.0. + """ + + def __init__( + self, + name: str, + data: _TYPE_FIELD_VALUE, + filename: str | None = None, + headers: typing.Mapping[str, str] | None = None, + header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + ): + self._name = name + self._filename = filename + self.data = data + self.headers: dict[str, str | None] = {} + if headers: + self.headers = dict(headers) + + if header_formatter is not None: + import warnings + + warnings.warn( + "The 'header_formatter' parameter is deprecated and " + "will be removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + self.header_formatter = header_formatter + else: + self.header_formatter = format_multipart_header_param + + @classmethod + def from_tuples( + cls, + fieldname: str, + value: _TYPE_FIELD_VALUE_TUPLE, + header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + ) -> RequestField: + """ + 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. + """ + filename: str | None + content_type: str | None + data: _TYPE_FIELD_VALUE + + 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: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Override this method to change how each multipart header + parameter is formatted. By default, this calls + :func:`format_multipart_header_param`. + + :param name: + The name of the parameter, an ASCII-only ``str``. + :param value: + The value of the parameter, a ``str`` or UTF-8 encoded + ``bytes``. + + :meta public: + """ + return self.header_formatter(name, value) + + def _render_parts( + self, + header_parts: ( + dict[str, _TYPE_FIELD_VALUE | None] + | typing.Sequence[tuple[str, _TYPE_FIELD_VALUE | None]] + ), + ) -> str: + """ + 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"; ...`. + """ + iterable: typing.Iterable[tuple[str, _TYPE_FIELD_VALUE | None]] + + parts = [] + if isinstance(header_parts, dict): + iterable = header_parts.items() + else: + iterable = header_parts + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return "; ".join(parts) + + def render_headers(self) -> str: + """ + 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(f"{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(f"{header_name}: {header_value}") + + lines.append("\r\n") + return "\r\n".join(lines) + + def make_multipart( + self, + content_disposition: str | None = None, + content_type: str | None = None, + content_location: str | None = None, + ) -> 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_disposition: + The 'Content-Disposition' of the request body. Defaults to 'form-data' + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + content_disposition = (content_disposition or "form-data") + "; ".join( + [ + "", + self._render_parts( + (("name", self._name), ("filename", self._filename)) + ), + ] + ) + + self.headers["Content-Disposition"] = content_disposition + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/filepost.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/filepost.py new file mode 100644 index 0000000000000000000000000000000000000000..14f70b05b4778f91137e4a9e7059d7514aa44d28 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/filepost.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import binascii +import codecs +import os +import typing +from io import BytesIO + +from .fields import _TYPE_FIELD_VALUE_TUPLE, RequestField + +writer = codecs.lookup("utf-8")[3] + +_TYPE_FIELDS_SEQUENCE = typing.Sequence[ + typing.Union[tuple[str, _TYPE_FIELD_VALUE_TUPLE], RequestField] +] +_TYPE_FIELDS = typing.Union[ + _TYPE_FIELDS_SEQUENCE, + typing.Mapping[str, _TYPE_FIELD_VALUE_TUPLE], +] + + +def choose_boundary() -> str: + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + return binascii.hexlify(os.urandom(16)).decode() + + +def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]: + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + iterable: typing.Iterable[RequestField | tuple[str, _TYPE_FIELD_VALUE_TUPLE]] + + if isinstance(fields, typing.Mapping): + iterable = fields.items() + else: + iterable = fields + + for field in iterable: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def encode_multipart_formdata( + fields: _TYPE_FIELDS, boundary: str | None = None +) -> tuple[bytes, str]: + """ + 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`). + Values are processed by :func:`urllib3.fields.RequestField.from_tuples`. + + :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(f"--{boundary}\r\n".encode("latin-1")) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, str): + writer(body).write(data) + else: + body.write(data) + + body.write(b"\r\n") + + body.write(f"--{boundary}--\r\n".encode("latin-1")) + + content_type = f"multipart/form-data; boundary={boundary}" + + return body.getvalue(), content_type diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__init__.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..133e1d8f237f6fddd557ae1c0e0cf738f7cc2748 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__init__.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from importlib.metadata import version + +__all__ = [ + "inject_into_urllib3", + "extract_from_urllib3", +] + +import typing + +orig_HTTPSConnection: typing.Any = None + + +def inject_into_urllib3() -> None: + # First check if h2 version is valid + h2_version = version("h2") + if not h2_version.startswith("4."): + raise ImportError( + "urllib3 v2 supports h2 version 4.x.x, currently " + f"the 'h2' module is compiled with {h2_version!r}. " + "See: https://github.com/urllib3/urllib3/issues/3290" + ) + + # Import here to avoid circular dependencies. + from .. import connection as urllib3_connection + from .. import util as urllib3_util + from ..connectionpool import HTTPSConnectionPool + from ..util import ssl_ as urllib3_util_ssl + from .connection import HTTP2Connection + + global orig_HTTPSConnection + orig_HTTPSConnection = urllib3_connection.HTTPSConnection + + HTTPSConnectionPool.ConnectionCls = HTTP2Connection + urllib3_connection.HTTPSConnection = HTTP2Connection # type: ignore[misc] + + # TODO: Offer 'http/1.1' as well, but for testing purposes this is handy. + urllib3_util.ALPN_PROTOCOLS = ["h2"] + urllib3_util_ssl.ALPN_PROTOCOLS = ["h2"] + + +def extract_from_urllib3() -> None: + from .. import connection as urllib3_connection + from .. import util as urllib3_util + from ..connectionpool import HTTPSConnectionPool + from ..util import ssl_ as urllib3_util_ssl + + HTTPSConnectionPool.ConnectionCls = orig_HTTPSConnection + urllib3_connection.HTTPSConnection = orig_HTTPSConnection # type: ignore[misc] + + urllib3_util.ALPN_PROTOCOLS = ["http/1.1"] + urllib3_util_ssl.ALPN_PROTOCOLS = ["http/1.1"] diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d906ef9d39dd7be2e04750f0cb57891dbd24f7cd Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/connection.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be1aeec670c92a52d8aca7aba83e0ef5325b36a2 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/connection.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/probe.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/probe.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86fcac4fe24da792e37d298317485332c01f049e Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/__pycache__/probe.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/connection.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..0a026da0a8357e324ded47b82b24042713b9bf06 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/connection.py @@ -0,0 +1,356 @@ +from __future__ import annotations + +import logging +import re +import threading +import types +import typing + +import h2.config +import h2.connection +import h2.events + +from .._base_connection import _TYPE_BODY +from .._collections import HTTPHeaderDict +from ..connection import HTTPSConnection, _get_default_user_agent +from ..exceptions import ConnectionError +from ..response import BaseHTTPResponse + +orig_HTTPSConnection = HTTPSConnection + +T = typing.TypeVar("T") + +log = logging.getLogger(__name__) + +RE_IS_LEGAL_HEADER_NAME = re.compile(rb"^[!#$%&'*+\-.^_`|~0-9a-z]+$") +RE_IS_ILLEGAL_HEADER_VALUE = re.compile(rb"[\0\x00\x0a\x0d\r\n]|^[ \r\n\t]|[ \r\n\t]$") + + +def _is_legal_header_name(name: bytes) -> bool: + """ + "An implementation that validates fields according to the definitions in Sections + 5.1 and 5.5 of [HTTP] only needs an additional check that field names do not + include uppercase characters." (https://httpwg.org/specs/rfc9113.html#n-field-validity) + + `http.client._is_legal_header_name` does not validate the field name according to the + HTTP 1.1 spec, so we do that here, in addition to checking for uppercase characters. + + This does not allow for the `:` character in the header name, so should not + be used to validate pseudo-headers. + """ + return bool(RE_IS_LEGAL_HEADER_NAME.match(name)) + + +def _is_illegal_header_value(value: bytes) -> bool: + """ + "A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed + (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position. A field + value MUST NOT start or end with an ASCII whitespace character (ASCII SP or HTAB, + 0x20 or 0x09)." (https://httpwg.org/specs/rfc9113.html#n-field-validity) + """ + return bool(RE_IS_ILLEGAL_HEADER_VALUE.search(value)) + + +class _LockedObject(typing.Generic[T]): + """ + A wrapper class that hides a specific object behind a lock. + The goal here is to provide a simple way to protect access to an object + that cannot safely be simultaneously accessed from multiple threads. The + intended use of this class is simple: take hold of it with a context + manager, which returns the protected object. + """ + + __slots__ = ( + "lock", + "_obj", + ) + + def __init__(self, obj: T): + self.lock = threading.RLock() + self._obj = obj + + def __enter__(self) -> T: + self.lock.acquire() + return self._obj + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + self.lock.release() + + +class HTTP2Connection(HTTPSConnection): + def __init__( + self, host: str, port: int | None = None, **kwargs: typing.Any + ) -> None: + self._h2_conn = self._new_h2_conn() + self._h2_stream: int | None = None + self._headers: list[tuple[bytes, bytes]] = [] + + if "proxy" in kwargs or "proxy_config" in kwargs: # Defensive: + raise NotImplementedError("Proxies aren't supported with HTTP/2") + + super().__init__(host, port, **kwargs) + + if self._tunnel_host is not None: + raise NotImplementedError("Tunneling isn't supported with HTTP/2") + + def _new_h2_conn(self) -> _LockedObject[h2.connection.H2Connection]: + config = h2.config.H2Configuration(client_side=True) + return _LockedObject(h2.connection.H2Connection(config=config)) + + def connect(self) -> None: + super().connect() + with self._h2_conn as conn: + conn.initiate_connection() + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + def putrequest( # type: ignore[override] + self, + method: str, + url: str, + **kwargs: typing.Any, + ) -> None: + """putrequest + This deviates from the HTTPConnection method signature since we never need to override + sending accept-encoding headers or the host header. + """ + if "skip_host" in kwargs: + raise NotImplementedError("`skip_host` isn't supported") + if "skip_accept_encoding" in kwargs: + raise NotImplementedError("`skip_accept_encoding` isn't supported") + + self._request_url = url or "/" + self._validate_path(url) # type: ignore[attr-defined] + + if ":" in self.host: + authority = f"[{self.host}]:{self.port or 443}" + else: + authority = f"{self.host}:{self.port or 443}" + + self._headers.append((b":scheme", b"https")) + self._headers.append((b":method", method.encode())) + self._headers.append((b":authority", authority.encode())) + self._headers.append((b":path", url.encode())) + + with self._h2_conn as conn: + self._h2_stream = conn.get_next_available_stream_id() + + def putheader(self, header: str | bytes, *values: str | bytes) -> None: # type: ignore[override] + # TODO SKIPPABLE_HEADERS from urllib3 are ignored. + header = header.encode() if isinstance(header, str) else header + header = header.lower() # A lot of upstream code uses capitalized headers. + if not _is_legal_header_name(header): + raise ValueError(f"Illegal header name {str(header)}") + + for value in values: + value = value.encode() if isinstance(value, str) else value + if _is_illegal_header_value(value): + raise ValueError(f"Illegal header value {str(value)}") + self._headers.append((header, value)) + + def endheaders(self, message_body: typing.Any = None) -> None: # type: ignore[override] + if self._h2_stream is None: + raise ConnectionError("Must call `putrequest` first.") + + with self._h2_conn as conn: + conn.send_headers( + stream_id=self._h2_stream, + headers=self._headers, + end_stream=(message_body is None), + ) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + self._headers = [] # Reset headers for the next request. + + def send(self, data: typing.Any) -> None: + """Send data to the server. + `data` can be: `str`, `bytes`, an iterable, or file-like objects + that support a .read() method. + """ + if self._h2_stream is None: + raise ConnectionError("Must call `putrequest` first.") + + with self._h2_conn as conn: + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + if hasattr(data, "read"): # file-like objects + while True: + chunk = data.read(self.blocksize) + if not chunk: + break + if isinstance(chunk, str): + chunk = chunk.encode() + conn.send_data(self._h2_stream, chunk, end_stream=False) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + conn.end_stream(self._h2_stream) + return + + if isinstance(data, str): # str -> bytes + data = data.encode() + + try: + if isinstance(data, bytes): + conn.send_data(self._h2_stream, data, end_stream=True) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + else: + for chunk in data: + conn.send_data(self._h2_stream, chunk, end_stream=False) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + conn.end_stream(self._h2_stream) + except TypeError: + raise TypeError( + "`data` should be str, bytes, iterable, or file. got %r" + % type(data) + ) + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + raise NotImplementedError( + "HTTP/2 does not support setting up a tunnel through a proxy" + ) + + def getresponse( # type: ignore[override] + self, + ) -> HTTP2Response: + status = None + data = bytearray() + with self._h2_conn as conn: + end_stream = False + while not end_stream: + # TODO: Arbitrary read value. + if received_data := self.sock.recv(65535): + events = conn.receive_data(received_data) + for event in events: + if isinstance(event, h2.events.ResponseReceived): + headers = HTTPHeaderDict() + for header, value in event.headers: + if header == b":status": + status = int(value.decode()) + else: + headers.add( + header.decode("ascii"), value.decode("ascii") + ) + + elif isinstance(event, h2.events.DataReceived): + data += event.data + conn.acknowledge_received_data( + event.flow_controlled_length, event.stream_id + ) + + elif isinstance(event, h2.events.StreamEnded): + end_stream = True + + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + assert status is not None + return HTTP2Response( + status=status, + headers=headers, + request_url=self._request_url, + data=bytes(data), + ) + + def request( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + *, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + **kwargs: typing.Any, + ) -> None: + """Send an HTTP/2 request""" + if "chunked" in kwargs: + # TODO this is often present from upstream. + # raise NotImplementedError("`chunked` isn't supported with HTTP/2") + pass + + if self.sock is not None: + self.sock.settimeout(self.timeout) + + self.putrequest(method, url) + + headers = headers or {} + for k, v in headers.items(): + if k.lower() == "transfer-encoding" and v == "chunked": + continue + else: + self.putheader(k, v) + + if b"user-agent" not in dict(self._headers): + self.putheader(b"user-agent", _get_default_user_agent()) + + if body: + self.endheaders(message_body=body) + self.send(body) + else: + self.endheaders() + + def close(self) -> None: + with self._h2_conn as conn: + try: + conn.close_connection() + if data := conn.data_to_send(): + self.sock.sendall(data) + except Exception: + pass + + # Reset all our HTTP/2 connection state. + self._h2_conn = self._new_h2_conn() + self._h2_stream = None + self._headers = [] + + super().close() + + +class HTTP2Response(BaseHTTPResponse): + # TODO: This is a woefully incomplete response object, but works for non-streaming. + def __init__( + self, + status: int, + headers: HTTPHeaderDict, + request_url: str, + data: bytes, + decode_content: bool = False, # TODO: support decoding + ) -> None: + super().__init__( + status=status, + headers=headers, + # Following CPython, we map HTTP versions to major * 10 + minor integers + version=20, + version_string="HTTP/2", + # No reason phrase in HTTP/2 + reason=None, + decode_content=decode_content, + request_url=request_url, + ) + self._data = data + self.length_remaining = 0 + + @property + def data(self) -> bytes: + return self._data + + def get_redirect_location(self) -> None: + return None + + def close(self) -> None: + pass diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/probe.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/probe.py new file mode 100644 index 0000000000000000000000000000000000000000..9ea900764f0885eafaac9454523417d86e33df2d --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/http2/probe.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import threading + + +class _HTTP2ProbeCache: + __slots__ = ( + "_lock", + "_cache_locks", + "_cache_values", + ) + + def __init__(self) -> None: + self._lock = threading.Lock() + self._cache_locks: dict[tuple[str, int], threading.RLock] = {} + self._cache_values: dict[tuple[str, int], bool | None] = {} + + def acquire_and_get(self, host: str, port: int) -> bool | None: + # By the end of this block we know that + # _cache_[values,locks] is available. + value = None + with self._lock: + key = (host, port) + try: + value = self._cache_values[key] + # If it's a known value we return right away. + if value is not None: + return value + except KeyError: + self._cache_locks[key] = threading.RLock() + self._cache_values[key] = None + + # If the value is unknown, we acquire the lock to signal + # to the requesting thread that the probe is in progress + # or that the current thread needs to return their findings. + key_lock = self._cache_locks[key] + key_lock.acquire() + try: + # If the by the time we get the lock the value has been + # updated we want to return the updated value. + value = self._cache_values[key] + + # In case an exception like KeyboardInterrupt is raised here. + except BaseException as e: # Defensive: + assert not isinstance(e, KeyError) # KeyError shouldn't be possible. + key_lock.release() + raise + + return value + + def set_and_release( + self, host: str, port: int, supports_http2: bool | None + ) -> None: + key = (host, port) + key_lock = self._cache_locks[key] + with key_lock: # Uses an RLock, so can be locked again from same thread. + if supports_http2 is None and self._cache_values[key] is not None: + raise ValueError( + "Cannot reset HTTP/2 support for origin after value has been set." + ) # Defensive: not expected in normal usage + + self._cache_values[key] = supports_http2 + key_lock.release() + + def _values(self) -> dict[tuple[str, int], bool | None]: + """This function is for testing purposes only. Gets the current state of the probe cache""" + with self._lock: + return {k: v for k, v in self._cache_values.items()} + + def _reset(self) -> None: + """This function is for testing purposes only. Reset the cache values""" + with self._lock: + self._cache_locks = {} + self._cache_values = {} + + +_HTTP2_PROBE_CACHE = _HTTP2ProbeCache() + +set_and_release = _HTTP2_PROBE_CACHE.set_and_release +acquire_and_get = _HTTP2_PROBE_CACHE.acquire_and_get +_values = _HTTP2_PROBE_CACHE._values +_reset = _HTTP2_PROBE_CACHE._reset + +__all__ = [ + "set_and_release", + "acquire_and_get", +] diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/poolmanager.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/poolmanager.py new file mode 100644 index 0000000000000000000000000000000000000000..c7a5f6efb09d6f6d9afc777d3fa08d06876bcc29 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/poolmanager.py @@ -0,0 +1,653 @@ +from __future__ import annotations + +import functools +import logging +import typing +import warnings +from types import TracebackType +from urllib.parse import urljoin + +from ._collections import HTTPHeaderDict, RecentlyUsedContainer +from ._request_methods import RequestMethods +from .connection import ProxyConfig +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + URLSchemeUnknown, +) +from .response import BaseHTTPResponse +from .util.connection import _TYPE_SOCKET_OPTIONS +from .util.proxy import connection_requires_http_tunnel +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import Url, parse_url + +if typing.TYPE_CHECKING: + import ssl + + from typing_extensions import Self + +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ca_cert_data", + "ssl_version", + "ssl_minimum_version", + "ssl_maximum_version", + "ca_cert_dir", + "ssl_context", + "key_password", + "server_hostname", +) +# Default value for `blocksize` - a new parameter introduced to +# http.client.HTTPConnection & http.client.HTTPSConnection in Python 3.7 +_DEFAULT_BLOCKSIZE = 16384 + + +class PoolKey(typing.NamedTuple): + """ + All known keyword arguments that could be provided to the pool manager, its + pools, or the underlying connections. + + All custom key schemes should include the fields in this key at a minimum. + """ + + key_scheme: str + key_host: str + key_port: int | None + key_timeout: Timeout | float | int | None + key_retries: Retry | bool | int | None + key_block: bool | None + key_source_address: tuple[str, int] | None + key_key_file: str | None + key_key_password: str | None + key_cert_file: str | None + key_cert_reqs: str | None + key_ca_certs: str | None + key_ca_cert_data: str | bytes | None + key_ssl_version: int | str | None + key_ssl_minimum_version: ssl.TLSVersion | None + key_ssl_maximum_version: ssl.TLSVersion | None + key_ca_cert_dir: str | None + key_ssl_context: ssl.SSLContext | None + key_maxsize: int | None + key_headers: frozenset[tuple[str, str]] | None + key__proxy: Url | None + key__proxy_headers: frozenset[tuple[str, str]] | None + key__proxy_config: ProxyConfig | None + key_socket_options: _TYPE_SOCKET_OPTIONS | None + key__socks_options: frozenset[tuple[str, str]] | None + key_assert_hostname: bool | str | None + key_assert_fingerprint: str | None + key_server_hostname: str | None + key_blocksize: int | None + + +def _default_key_normalizer( + key_class: type[PoolKey], request_context: dict[str, typing.Any] +) -> PoolKey: + """ + 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 + + # Default key_blocksize to _DEFAULT_BLOCKSIZE if missing from the context + if context.get("key_blocksize") is None: + context["key_blocksize"] = _DEFAULT_BLOCKSIZE + + 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: + + .. code-block:: python + + from pip._vendor import urllib3 + + http = urllib3.PoolManager(num_pools=2) + + resp1 = http.request("GET", "https://google.com/") + resp2 = http.request("GET", "https://google.com/mail") + resp3 = http.request("GET", "https://yahoo.com/") + + print(len(http.pools)) + # 2 + + """ + + proxy: Url | None = None + proxy_config: ProxyConfig | None = None + + def __init__( + self, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + **connection_pool_kw: typing.Any, + ) -> None: + super().__init__(headers) + # PoolManager handles redirects itself in PoolManager.urlopen(). + # It always passes redirect=False to the underlying connection pool to + # suppress per-pool redirect handling. If the user supplied a non-Retry + # value (int/bool/etc) for retries and we let the pool normalize it + # while redirect=False, the resulting Retry object would have redirect + # handling disabled, which can interfere with PoolManager's own + # redirect logic. Normalize here so redirects remain governed solely by + # PoolManager logic. + if "retries" in connection_pool_kw: + retries = connection_pool_kw["retries"] + if not isinstance(retries, Retry): + retries = Retry.from_int(retries) + connection_pool_kw = connection_pool_kw.copy() + connection_pool_kw["retries"] = retries + self.connection_pool_kw = connection_pool_kw + + self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool] + self.pools = RecentlyUsedContainer(num_pools) + + # 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) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> typing.Literal[False]: + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool( + self, + scheme: str, + host: str, + port: int, + request_context: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + """ + 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: type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Default blocksize to _DEFAULT_BLOCKSIZE if missing or explicitly + # set to 'None' in the request_context. + if request_context.get("blocksize") is None: + request_context["blocksize"] = _DEFAULT_BLOCKSIZE + + # 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) -> None: + """ + 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: str | None, + port: int | None = None, + scheme: str | None = "http", + pool_kwargs: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + """ + 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: dict[str, typing.Any] + ) -> HTTPConnectionPool: + """ + 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. + """ + if "strict" in request_context: + warnings.warn( + "The 'strict' parameter is no longer needed on Python 3+. " + "This will raise an error in urllib3 v3.0.", + FutureWarning, + ) + request_context.pop("strict") + + 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: PoolKey, request_context: dict[str, typing.Any] + ) -> HTTPConnectionPool: + """ + 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: str, pool_kwargs: dict[str, typing.Any] | None = None + ) -> HTTPConnectionPool: + """ + 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: dict[str, typing.Any] | None + ) -> dict[str, typing.Any]: + """ + 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: Url) -> bool: + """ + 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 urlopen( # type: ignore[override] + self, method: str, url: str, redirect: bool = True, **kw: typing.Any + ) -> BaseHTTPResponse: + """ + 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) + + if u.scheme is None: + warnings.warn( + "URLs without a scheme (ie 'https://') are deprecated and will raise an error " + "in urllib3 v3.0. To avoid this FutureWarning ensure all URLs " + "start with 'https://' or 'http://'. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2920", + category=FutureWarning, + stacklevel=2, + ) + + 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 + + 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) + + if response.status == 303: + # Change the method according to RFC 9110, Section 15.4.4. + method = "GET" + # And lose the body not to transfer anything sensitive. + kw["body"] = None + kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() + + retries = kw.get("retries", response.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 + ): + new_headers = kw["headers"].copy() + for header in kw["headers"]: + if header.lower() in retries.remove_headers_on_redirect: + new_headers.pop(header, None) + kw["headers"] = new_headers + + 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. + + :param proxy_assert_hostname: + The hostname of the certificate to verify against. + + :param proxy_assert_fingerprint: + The fingerprint of the certificate to verify against. + + Example: + + .. code-block:: python + + from pip._vendor import urllib3 + + proxy = urllib3.ProxyManager("https://localhost:3128/") + + resp1 = proxy.request("GET", "http://google.com/") + resp2 = proxy.request("GET", "http://httpbin.org/") + + # One pool was shared by both plain HTTP requests. + print(len(proxy.pools)) + # 1 + + resp3 = proxy.request("GET", "https://httpbin.org/") + resp4 = proxy.request("GET", "https://twitter.com/") + + # A separate pool was added for each HTTPS target. + print(len(proxy.pools)) + # 3 + + """ + + def __init__( + self, + proxy_url: str, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + proxy_headers: typing.Mapping[str, str] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + use_forwarding_for_https: bool = False, + proxy_assert_hostname: None | str | typing.Literal[False] = None, + proxy_assert_fingerprint: str | None = None, + **connection_pool_kw: typing.Any, + ) -> None: + if isinstance(proxy_url, HTTPConnectionPool): + str_proxy_url = f"{proxy_url.scheme}://{proxy_url.host}:{proxy_url.port}" + else: + str_proxy_url = proxy_url + proxy = parse_url(str_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, + proxy_assert_hostname, + proxy_assert_fingerprint, + ) + + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config + + super().__init__(num_pools, headers, **connection_pool_kw) + + def connection_from_host( + self, + host: str | None, + port: int | None = None, + scheme: str | None = "http", + pool_kwargs: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + if scheme == "https": + return super().connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs + ) + + return super().connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs # type: ignore[union-attr] + ) + + def _set_proxy_headers( + self, url: str, headers: typing.Mapping[str, str] | None = None + ) -> typing.Mapping[str, str]: + """ + 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( # type: ignore[override] + self, method: str, url: str, redirect: bool = True, **kw: typing.Any + ) -> BaseHTTPResponse: + "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().urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url: str, **kw: typing.Any) -> ProxyManager: + return ProxyManager(proxy_url=url, **kw) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/py.typed b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..5f3ea3d919363f08ab03edbc85b6099bc4df5647 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/py.typed @@ -0,0 +1,2 @@ +# Instruct type checkers to look for inline type annotations in this package. +# See PEP 561. diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/response.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/response.py new file mode 100644 index 0000000000000000000000000000000000000000..1eb0b0a274328c5987b46e66f8f56fb9cc752fc6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/response.py @@ -0,0 +1,1487 @@ +from __future__ import annotations + +import collections +import io +import json as _json +import logging +import socket +import sys +import typing +import warnings +import zlib +from contextlib import contextmanager +from http.client import HTTPMessage as _HttplibHTTPMessage +from http.client import HTTPResponse as _HttplibHTTPResponse +from socket import timeout as SocketTimeout + +if typing.TYPE_CHECKING: + from ._base_connection import BaseHTTPConnection + +brotli = None + +from . import util +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPConnection, HTTPException +from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, + DependencyWarning, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, +) +from .util.response import is_fp_closed, is_response_to_head +from .util.retry import Retry + +if typing.TYPE_CHECKING: + from .connectionpool import HTTPConnectionPool + +log = logging.getLogger(__name__) + + +class ContentDecoder: + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + raise NotImplementedError() + + @property + def has_unconsumed_tail(self) -> bool: + raise NotImplementedError() + + def flush(self) -> bytes: + raise NotImplementedError() + + +class DeflateDecoder(ContentDecoder): + def __init__(self) -> None: + self._first_try = True + self._first_try_data = b"" + self._unfed_data = b"" + self._obj = zlib.decompressobj() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + data = self._unfed_data + data + self._unfed_data = b"" + if not data and not self._obj.unconsumed_tail: + return data + original_max_length = max_length + if original_max_length < 0: + max_length = 0 + elif original_max_length == 0: + # We should not pass 0 to the zlib decompressor because 0 is + # the default value that will make zlib decompress without a + # length limit. + # Data should be stored for subsequent calls. + self._unfed_data = data + return b"" + + # Subsequent calls always reuse `self._obj`. zlib requires + # passing the unconsumed tail if decompression is to continue. + if not self._first_try: + return self._obj.decompress( + self._obj.unconsumed_tail + data, max_length=max_length + ) + + # First call tries with RFC 1950 ZLIB format. + self._first_try_data += data + try: + decompressed = self._obj.decompress(data, max_length=max_length) + if decompressed: + self._first_try = False + self._first_try_data = b"" + return decompressed + # On failure, it falls back to RFC 1951 DEFLATE format. + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress( + self._first_try_data, max_length=original_max_length + ) + finally: + self._first_try_data = b"" + + @property + def has_unconsumed_tail(self) -> bool: + return bool(self._unfed_data) or ( + bool(self._obj.unconsumed_tail) and not self._first_try + ) + + def flush(self) -> bytes: + return self._obj.flush() + + +class GzipDecoderState: + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(ContentDecoder): + def __init__(self) -> None: + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + self._unconsumed_tail = b"" + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA: + return bytes(ret) + + if max_length == 0: + # We should not pass 0 to the zlib decompressor because 0 is + # the default value that will make zlib decompress without a + # length limit. + # Data should be stored for subsequent calls. + self._unconsumed_tail += data + return b"" + + # zlib requires passing the unconsumed tail to the subsequent + # call if decompression is to continue. + data = self._unconsumed_tail + data + if not data and self._obj.eof: + return bytes(ret) + + while True: + try: + ret += self._obj.decompress( + data, max_length=max(max_length - len(ret), 0) + ) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + self._unconsumed_tail = b"" + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + + self._unconsumed_tail = data = ( + self._obj.unconsumed_tail or self._obj.unused_data + ) + if max_length > 0 and len(ret) >= max_length: + break + + if not data: + return bytes(ret) + # When the end of a gzip member is reached, a new decompressor + # must be created for unused (possibly future) data. + if self._obj.eof: + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + return bytes(ret) + + @property + def has_unconsumed_tail(self) -> bool: + return bool(self._unconsumed_tail) + + def flush(self) -> bytes: + return self._obj.flush() + + +if brotli is not None: + + class BrotliDecoder(ContentDecoder): + # 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() + if hasattr(self._obj, "decompress"): + setattr(self, "_decompress", self._obj.decompress) + else: + setattr(self, "_decompress", self._obj.process) + + # Requires Brotli >= 1.2.0 for `output_buffer_limit`. + def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: + raise NotImplementedError() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + try: + if max_length > 0: + return self._decompress(data, output_buffer_limit=max_length) + else: + return self._decompress(data) + except TypeError: + # Fallback for Brotli/brotlicffi/brotlipy versions without + # the `output_buffer_limit` parameter. + warnings.warn( + "Brotli >= 1.2.0 is required to prevent decompression bombs.", + DependencyWarning, + ) + return self._decompress(data) + + @property + def has_unconsumed_tail(self) -> bool: + try: + return not self._obj.can_accept_more_data() + except AttributeError: + return False + + def flush(self) -> bytes: + if hasattr(self._obj, "flush"): + return self._obj.flush() # type: ignore[no-any-return] + return b"" + + +try: + if sys.version_info >= (3, 14): + from compression import zstd + else: + from backports import zstd +except ImportError: + HAS_ZSTD = False +else: + HAS_ZSTD = True + + class ZstdDecoder(ContentDecoder): + def __init__(self) -> None: + self._obj = zstd.ZstdDecompressor() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + if not data and not self.has_unconsumed_tail: + return b"" + if self._obj.eof: + data = self._obj.unused_data + data + self._obj = zstd.ZstdDecompressor() + part = self._obj.decompress(data, max_length=max_length) + length = len(part) + data_parts = [part] + # Every loop iteration is supposed to read data from a separate frame. + # The loop breaks when: + # - enough data is read; + # - no more unused data is available; + # - end of the last read frame has not been reached (i.e., + # more data has to be fed). + while ( + self._obj.eof + and self._obj.unused_data + and (max_length < 0 or length < max_length) + ): + unused_data = self._obj.unused_data + if not self._obj.needs_input: + self._obj = zstd.ZstdDecompressor() + part = self._obj.decompress( + unused_data, + max_length=(max_length - length) if max_length > 0 else -1, + ) + if part_length := len(part): + data_parts.append(part) + length += part_length + elif self._obj.needs_input: + break + return b"".join(data_parts) + + @property + def has_unconsumed_tail(self) -> bool: + return not (self._obj.needs_input or self._obj.eof) or bool( + self._obj.unused_data + ) + + def flush(self) -> bytes: + if not self._obj.eof: + raise DecodeError("Zstandard data is incomplete") + return b"" + + +class MultiDecoder(ContentDecoder): + """ + 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. + """ + + # Maximum allowed number of chained HTTP encodings in the + # Content-Encoding header. + max_decode_links = 5 + + def __init__(self, modes: str) -> None: + encodings = [m.strip() for m in modes.split(",")] + if len(encodings) > self.max_decode_links: + raise DecodeError( + "Too many content encodings in the chain: " + f"{len(encodings)} > {self.max_decode_links}" + ) + self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self) -> bytes: + return self._decoders[0].flush() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + if max_length <= 0: + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + ret = bytearray() + # Every while loop iteration goes through all decoders once. + # It exits when enough data is read or no more data can be read. + # It is possible that the while loop iteration does not produce + # any data because we retrieve up to `max_length` from every + # decoder, and the amount of bytes may be insufficient for the + # next decoder to produce enough/any output. + while True: + any_data = False + for d in reversed(self._decoders): + data = d.decompress(data, max_length=max_length - len(ret)) + if data: + any_data = True + # We should not break when no data is returned because + # next decoders may produce data even with empty input. + ret += data + if not any_data or len(ret) >= max_length: + return bytes(ret) + data = b"" + + @property + def has_unconsumed_tail(self) -> bool: + return any(d.has_unconsumed_tail for d in self._decoders) + + +def _get_decoder(mode: str) -> ContentDecoder: + if "," in mode: + return MultiDecoder(mode) + + # According to RFC 9110 section 8.4.1.3, recipients should + # consider x-gzip equivalent to gzip + if mode in ("gzip", "x-gzip"): + return GzipDecoder() + + if brotli is not None and mode == "br": + return BrotliDecoder() + + if HAS_ZSTD and mode == "zstd": + return ZstdDecoder() + + return DeflateDecoder() + + +class BytesQueueBuffer: + """Memory-efficient bytes buffer + + To return decoded data in read() and still follow the BufferedIOBase API, we need a + buffer to always return the correct amount of bytes. + + This buffer should be filled using calls to put() + + Our maximum memory usage is determined by the sum of the size of: + + * self.buffer, which contains the full data + * the largest chunk that we will copy in get() + """ + + def __init__(self) -> None: + self.buffer: typing.Deque[bytes | memoryview[bytes]] = collections.deque() + self._size: int = 0 + + def __len__(self) -> int: + return self._size + + def put(self, data: bytes) -> None: + self.buffer.append(data) + self._size += len(data) + + def get(self, n: int) -> bytes: + if n == 0: + return b"" + elif not self.buffer: + raise RuntimeError("buffer is empty") + elif n < 0: + raise ValueError("n should be > 0") + + if len(self.buffer[0]) == n and isinstance(self.buffer[0], bytes): + self._size -= n + return self.buffer.popleft() + + fetched = 0 + ret = io.BytesIO() + while fetched < n: + remaining = n - fetched + chunk = self.buffer.popleft() + chunk_length = len(chunk) + if remaining < chunk_length: + chunk = memoryview(chunk) + left_chunk, right_chunk = chunk[:remaining], chunk[remaining:] + ret.write(left_chunk) + self.buffer.appendleft(right_chunk) + self._size -= remaining + break + else: + ret.write(chunk) + self._size -= chunk_length + fetched += chunk_length + + if not self.buffer: + break + + return ret.getvalue() + + def get_all(self) -> bytes: + buffer = self.buffer + if not buffer: + assert self._size == 0 + return b"" + if len(buffer) == 1: + result = buffer.pop() + if isinstance(result, memoryview): + result = result.tobytes() + else: + ret = io.BytesIO() + ret.writelines(buffer.popleft() for _ in range(len(buffer))) + result = ret.getvalue() + self._size = 0 + return result + + +class BaseHTTPResponse(io.IOBase): + CONTENT_DECODERS = ["gzip", "x-gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + if HAS_ZSTD: + CONTENT_DECODERS += ["zstd"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + DECODER_ERROR_CLASSES: tuple[type[Exception], ...] = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + if HAS_ZSTD: + DECODER_ERROR_CLASSES += (zstd.ZstdError,) + + def __init__( + self, + *, + headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, + status: int, + version: int, + version_string: str, + reason: str | None, + decode_content: bool, + request_url: str | None, + retries: Retry | None = None, + ) -> None: + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) # type: ignore[arg-type] + self.status = status + self.version = version + self.version_string = version_string + self.reason = reason + self.decode_content = decode_content + self._has_decoded_content = False + self._request_url: str | None = request_url + self.retries = retries + + self.chunked = False + 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 + + self._decoder: ContentDecoder | None = None + self.length_remaining: int | None + + def get_redirect_location(self) -> str | None | typing.Literal[False]: + """ + 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 + + @property + def data(self) -> bytes: + raise NotImplementedError() + + def json(self) -> typing.Any: + """ + Deserializes the body of the HTTP response as a Python object. + + The body of the HTTP response must be encoded using UTF-8, as per + `RFC 8529 Section 8.1 `_. + + To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to + your custom decoder instead. + + If the body of the HTTP response is not decodable to UTF-8, a + `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a + valid JSON document, a `json.JSONDecodeError` will be raised. + + Read more :ref:`here `. + + :returns: The body of the HTTP response as a Python object. + """ + data = self.data.decode("utf-8") + return _json.loads(data) + + @property + def url(self) -> str | None: + raise NotImplementedError() + + @url.setter + def url(self, url: str | None) -> None: + raise NotImplementedError() + + @property + def connection(self) -> BaseHTTPConnection | None: + raise NotImplementedError() + + @property + def retries(self) -> Retry | None: + return self._retries + + @retries.setter + def retries(self, retries: Retry | None) -> None: + # Override the request_url if retries has a redirect location. + if retries is not None and retries.history: + self.url = retries.history[-1].redirect_location + self._retries = retries + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Iterator[bytes]: + raise NotImplementedError() + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, + cache_content: bool = False, + ) -> bytes: + raise NotImplementedError() + + def read1( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> bytes: + raise NotImplementedError() + + def read_chunked( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> typing.Iterator[bytes]: + raise NotImplementedError() + + def release_conn(self) -> None: + raise NotImplementedError() + + def drain_conn(self) -> None: + raise NotImplementedError() + + def shutdown(self) -> None: + raise NotImplementedError() + + def close(self) -> None: + raise NotImplementedError() + + def _init_decoder(self) -> None: + """ + 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 encodings: + self._decoder = _get_decoder(content_encoding) + + def _decode( + self, + data: bytes, + decode_content: bool | None, + flush_decoder: bool, + max_length: int | None = None, + ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + if self._has_decoded_content: + raise RuntimeError( + "Calling read(decode_content=False) is not supported after " + "read(decode_content=True) was called." + ) + return data + + if max_length is None or flush_decoder: + max_length = -1 + + try: + if self._decoder: + data = self._decoder.decompress(data, max_length=max_length) + self._has_decoded_content = True + 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, + ) from e + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self) -> bytes: + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + return self._decoder.decompress(b"") + self._decoder.flush() + return b"" + + # Compatibility methods for `io` module + def readinto(self, b: bytearray | memoryview[int]) -> int: + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + # Methods used by dependent libraries + def getheaders(self) -> HTTPHeaderDict: + return self.headers + + def getheader(self, name: str, default: str | None = None) -> str | None: + return self.headers.get(name, default) + + # Compatibility method for http.cookiejar + def info(self) -> HTTPHeaderDict: + return self.headers + + def geturl(self) -> str | None: + return self.url + + +class HTTPResponse(BaseHTTPResponse): + """ + 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. + """ + + def __init__( + self, + body: _TYPE_BODY = "", + headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, + status: int = 0, + version: int = 0, + version_string: str = "HTTP/?", + reason: str | None = None, + preload_content: bool = True, + decode_content: bool = True, + original_response: _HttplibHTTPResponse | None = None, + pool: HTTPConnectionPool | None = None, + connection: HTTPConnection | None = None, + msg: _HttplibHTTPMessage | None = None, + retries: Retry | None = None, + enforce_content_length: bool = True, + request_method: str | None = None, + request_url: str | None = None, + auto_close: bool = True, + sock_shutdown: typing.Callable[[int], None] | None = None, + ) -> None: + super().__init__( + headers=headers, + status=status, + version=version, + version_string=version_string, + reason=reason, + decode_content=decode_content, + request_url=request_url, + retries=retries, + ) + + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close + + self._body = None + self._uncached_read_occurred = False + self._fp: _HttplibHTTPResponse | None = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + + if body and isinstance(body, (str, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, "read"): + self._fp = body # type: ignore[assignment] + self._sock_shutdown = sock_shutdown + + # Are we using the chunked-style of transfer encoding? + self.chunk_left: int | None = None + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # Used to return the correct amount of bytes for partial read()s + self._decoded_buffer = BytesQueueBuffer() + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def release_conn(self) -> None: + if not self._pool or not self._connection: + return None + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self) -> None: + """ + 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._raw_read() + except (HTTPError, OSError, BaseSSLError, HTTPException): + pass + if self._has_decoded_content: + # `_raw_read` skips decompression, so we should clean up the + # decoder to avoid keeping unnecessary data in memory. + self._decoded_buffer = BytesQueueBuffer() + self._decoder = None + + @property + def data(self) -> bytes: + # For backwards-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body # type: ignore[return-value] + + if self._fp: + return self.read(cache_content=True) + + return None # type: ignore[return-value] + + @property + def connection(self) -> HTTPConnection | None: + return self._connection + + def isclosed(self) -> bool: + return is_fp_closed(self._fp) + + def tell(self) -> int: + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:`HTTPResponse.read` + if bytes are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method: str | None) -> int | None: + """ + Set initial length value for Response content if available. + """ + length: int | None + content_length: str | None = self.headers.get("content-length") + + if content_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 = {int(val) for val in content_length.split(",")} + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % content_length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + else: # if content_length is None + 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 + + @contextmanager + def _error_catcher(self) -> typing.Generator[None]: + """ + 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 as e: + # 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.") from e # type: ignore[arg-type] + + except BaseSSLError as e: + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) from e + + except IncompleteRead as e: + if ( + e.expected is not None + and e.partial is not None + and e.expected == -e.partial + ): + arg = "Response may not contain content." + else: + arg = f"Connection broken: {e!r}" + raise ProtocolError(arg, e) from e + + except (HTTPException, OSError) as e: + raise ProtocolError(f"Connection broken: {e!r}", e) from 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: int | None = None, + *, + read1: bool = False, + ) -> bytes: + """ + 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. + + This happens to urllib3 injected with pyOpenSSL-backed SSL-support. + """ + assert self._fp + c_int_max = 2**31 - 1 + if ( + (amt and amt > c_int_max) + or ( + amt is None + and self.length_remaining + and self.length_remaining > c_int_max + ) + ) and util.IS_PYOPENSSL: + if read1: + return self._fp.read1(c_int_max) + 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() + elif read1: + return self._fp.read1(amt) if amt is not None else self._fp.read1() + else: + # StringIO doesn't like amt=None + return self._fp.read(amt) if amt is not None else self._fp.read() + + def _raw_read( + self, + amt: int | None = None, + *, + read1: bool = False, + ) -> bytes: + """ + Reads `amt` of bytes from the socket. + """ + if self._fp is None: + return None # type: ignore[return-value] + + fp_closed = getattr(self._fp, "closed", False) + + with self._error_catcher(): + data = self._fp_read(amt, read1=read1) if not fp_closed else b"" + if amt is not None and 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() + if ( + self.enforce_content_length + and self.length_remaining is not None + and self.length_remaining != 0 + ): + # 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) + elif read1 and ( + (amt != 0 and not data) or self.length_remaining == len(data) + ): + # All data has been read, but `self._fp.read1` in + # CPython 3.12 and older doesn't always close + # `http.client.HTTPResponse`, so we close it here. + # See https://github.com/python/cpython/issues/113199 + self._fp.close() + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + return data + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, + cache_content: bool = False, + ) -> bytes: + """ + 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 amt and amt < 0: + # Negative numbers and `None` should be treated the same. + amt = None + elif amt is not None: + cache_content = False + + if ( + self._decoder + and self._decoder.has_unconsumed_tail + and len(self._decoded_buffer) < amt + ): + decoded_data = self._decode( + b"", + decode_content, + flush_decoder=False, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + if len(self._decoded_buffer) >= amt: + return self._decoded_buffer.get(amt) + + data = self._raw_read(amt) + if not cache_content: + self._uncached_read_occurred = True + + flush_decoder = amt is None or (amt != 0 and not data) + + if ( + not data + and len(self._decoded_buffer) == 0 + and not (self._decoder and self._decoder.has_unconsumed_tail) + ): + return data + + if amt is None: + data = self._decode(data, decode_content, flush_decoder) + # It's possible that there is buffered decoded data after a + # partial read. + if decode_content and len(self._decoded_buffer) > 0: + self._decoded_buffer.put(data) + data = self._decoded_buffer.get_all() + + if cache_content and not self._uncached_read_occurred: + self._body = data + else: + # do not waste memory on buffer when not decoding + if not decode_content: + if self._has_decoded_content: + raise RuntimeError( + "Calling read(decode_content=False) is not supported after " + "read(decode_content=True) was called." + ) + return data + + decoded_data = self._decode( + data, + decode_content, + flush_decoder, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + + while len(self._decoded_buffer) < amt and data: + # TODO make sure to initially read enough data to get past the headers + # For example, the GZ file header takes 10 bytes, we don't want to read + # it one byte at a time + data = self._raw_read(amt) + decoded_data = self._decode( + data, + decode_content, + flush_decoder, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + data = self._decoded_buffer.get(amt) + + return data + + def read1( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> bytes: + """ + Similar to ``http.client.HTTPResponse.read1`` and documented + in :meth:`io.BufferedReader.read1`, but with an additional parameter: + ``decode_content``. + + :param amt: + How much of the content to read. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if decode_content is None: + decode_content = self.decode_content + if amt and amt < 0: + # Negative numbers and `None` should be treated the same. + amt = None + # try and respond without going to the network + if self._has_decoded_content: + if not decode_content: + raise RuntimeError( + "Calling read1(decode_content=False) is not supported after " + "read1(decode_content=True) was called." + ) + if ( + self._decoder + and self._decoder.has_unconsumed_tail + and (amt is None or len(self._decoded_buffer) < amt) + ): + decoded_data = self._decode( + b"", + decode_content, + flush_decoder=False, + max_length=( + amt - len(self._decoded_buffer) if amt is not None else None + ), + ) + self._decoded_buffer.put(decoded_data) + if len(self._decoded_buffer) > 0: + if amt is None: + return self._decoded_buffer.get_all() + return self._decoded_buffer.get(amt) + if amt == 0: + return b"" + + # FIXME, this method's type doesn't say returning None is possible + data = self._raw_read(amt, read1=True) + self._uncached_read_occurred = True + if not decode_content or data is None: + return data + + self._init_decoder() + while True: + flush_decoder = not data + decoded_data = self._decode( + data, decode_content, flush_decoder, max_length=amt + ) + self._decoded_buffer.put(decoded_data) + if decoded_data or flush_decoder: + break + data = self._raw_read(8192, read1=True) + + if amt is None: + return self._decoded_buffer.get_all() + return self._decoded_buffer.get(amt) + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + 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 amt == 0: + return + + if self.chunked and self.supports_chunked_reads(): + yield from self.read_chunked(amt, decode_content=decode_content) + else: + while ( + not is_fp_closed(self._fp) + or len(self._decoded_buffer) > 0 + or (self._decoder and self._decoder.has_unconsumed_tail) + ): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + # Overrides from io.IOBase + def readable(self) -> bool: + return True + + def shutdown(self) -> None: + if not self._sock_shutdown: + raise ValueError("Cannot shutdown socket as self._sock_shutdown is not set") + if self._connection is None: + raise RuntimeError( + "Cannot shutdown as connection has already been released to the pool" + ) + self._sock_shutdown(socket.SHUT_RD) + + def close(self) -> None: + self._sock_shutdown = None + + if not self.closed and self._fp: + self._fp.close() + + if self._connection: + self._connection.close() + + if not self.auto_close: + io.IOBase.close(self) + + @property + def closed(self) -> bool: + if not self.auto_close: + return io.IOBase.closed.__get__(self) # type: ignore[no-any-return] + 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) -> int: + if self._fp is None: + raise OSError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise OSError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) + + def flush(self) -> None: + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): + return self._fp.flush() + + def supports_chunked_reads(self) -> bool: + """ + 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) -> None: + # 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 None + line = self._fp.fp.readline() # type: ignore[union-attr] + line = line.split(b";", 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + self.close() + if line: + # Invalid chunked protocol response, abort. + raise InvalidChunkLength(self, line) from None + else: + # Truncated at start of next chunk + raise ProtocolError("Response ended prematurely") from None + + def _handle_chunk(self, amt: int | None) -> bytes: + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + returned_chunk = chunk + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif self.chunk_left is not None and amt < self.chunk_left: + value = self._fp._safe_read(amt) # type: ignore[union-attr] + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) # type: ignore[union-attr] + self._fp._safe_read(2) # type: ignore[union-attr] # 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) # type: ignore[union-attr] + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk # type: ignore[no-any-return] + + def read_chunked( + self, amt: int | None = None, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + 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 None + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: # type: ignore[union-attr] + return None + + if amt == 0: + return + elif amt and amt < 0: + # Negative numbers and `None` should be treated the same, + # but httplib handles only `None` correctly. + amt = None + + while True: + # First, check if any data is left in the decoder's buffer. + if self._decoder and self._decoder.has_unconsumed_tail: + chunk = b"" + else: + self._update_chunk_length() + self._uncached_read_occurred = True + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode( + chunk, + decode_content=decode_content, + flush_decoder=False, + max_length=amt, + ) + 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 self._fp is not None: + 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() + + @property + def url(self) -> str | None: + """ + 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. + """ + return self._request_url + + @url.setter + def url(self, url: str | None) -> None: + self._request_url = url + + def __iter__(self) -> typing.Iterator[bytes]: + buffer: list[bytes] = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunks = chunk.split(b"\n") + yield b"".join(buffer) + chunks[0] + b"\n" + for x in chunks[1:-1]: + yield x + b"\n" + if chunks[-1]: + buffer = [chunks[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__init__.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..534126033c083203649022fa9b753a433f005556 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__init__.py @@ -0,0 +1,42 @@ +# For backwards compatibility, provide imports that used to be here. +from __future__ import annotations + +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, + IS_PYOPENSSL, + SSLContext, + assert_fingerprint, + create_urllib3_context, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import Timeout +from .url import Url, parse_url +from .wait import wait_for_read, wait_for_write + +__all__ = ( + "IS_PYOPENSSL", + "SSLContext", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "create_urllib3_context", + "is_connection_dropped", + "is_fp_closed", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", +) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b87aadb518e1bf882a92284203f4b4ab6c1d5eb8 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a9278bafe7cf7ff92b0fc5b727dfff7e3de1550 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fb19d5c5690c8ca0977185c6ef98a1f5fd66b3e Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b429eb0469e8fa4d680a755abe3a2f3ea5b93f0 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfabdc9815c2495848d62e8699ef623979576a8d Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67db80c7b2da41d73c2d6c6d4918d4bdc68b11ef Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd13c856ab1e1222004e78d203435567de040622 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98acf08938d2052ec583413aaec0873c8fd118ab Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e186b30df4dfe227d9d0d6722cd7bbdb124191a Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..536e1f1796f7873287c1aab7b83b265d34a0d1c8 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a3b5e2d60e72ca23c1fc09be8979e757f5eb92e Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/util.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/util.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..afd09642ac73359826278dc674597b9c463816fb Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/util.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ef329658c8283fbbcad68f9940ec32d14787536 Binary files /dev/null and b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/connection.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..f92519ee9124e91e5da7d60ccc3f274312ed3514 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/connection.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import socket +import typing + +from ..exceptions import LocationParseError +from .timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT + +_TYPE_SOCKET_OPTIONS = list[tuple[int, int, typing.Union[int, bytes]]] + +if typing.TYPE_CHECKING: + from .._base_connection import BaseHTTPConnection + + +def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + :param conn: :class:`urllib3.connection.HTTPConnection` object. + """ + return not conn.is_connected + + +# 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: tuple[str, int], + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + socket_options: _TYPE_SOCKET_OPTIONS | None = None, +) -> socket.socket: + """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: + raise LocationParseError(f"'{host}', label empty or too long") from 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 _DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + # Break explicitly a reference cycle + err = None + return sock + + except OSError as _: + err = _ + if sock is not None: + sock.close() + + if err is not None: + try: + raise err + finally: + # Break explicitly a reference cycle + err = None + else: + raise OSError("getaddrinfo returns an empty list") + + +def _set_socket_options( + sock: socket.socket, options: _TYPE_SOCKET_OPTIONS | None +) -> None: + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family() -> socket.AddressFamily: + """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: str) -> bool: + """Returns True if the system can bind an IPv6 address.""" + sock = None + has_ipv6 = 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/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/proxy.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/proxy.py new file mode 100644 index 0000000000000000000000000000000000000000..908fc6621d0afbed16bde2c1957a5cf28d3a84d8 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/proxy.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import typing + +from .url import Url + +if typing.TYPE_CHECKING: + from ..connection import ProxyConfig + + +def connection_requires_http_tunnel( + proxy_url: Url | None = None, + proxy_config: ProxyConfig | None = None, + destination_scheme: str | None = None, +) -> bool: + """ + 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 diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/request.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/request.py new file mode 100644 index 0000000000000000000000000000000000000000..b0d271777bdc60d0a2fc507471aa6a4143856c72 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/request.py @@ -0,0 +1,254 @@ +from __future__ import annotations + +import io +import sys +import typing +from base64 import b64encode +from enum import Enum + +from ..exceptions import UnrewindableBodyError +from .util import to_bytes + +if typing.TYPE_CHECKING: + from typing import Final + +# 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: + if sys.version_info >= (3, 14): + from compression import zstd as _unused_module_zstd # noqa: F401 + else: + from backports import zstd as _unused_module_zstd # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",zstd" + + +class _TYPE_FAILEDTELL(Enum): + token = 0 + + +_FAILEDTELL: Final[_TYPE_FAILEDTELL] = _TYPE_FAILEDTELL.token + +_TYPE_BODY_POSITION = typing.Union[int, _TYPE_FAILEDTELL] + +# When sending a request with these methods we aren't expecting +# a body so don't need to set an explicit 'Content-Length: 0' +# The reason we do this in the negative instead of tracking methods +# which 'should' have a body is because unknown methods should be +# treated as if they were 'POST' which *does* expect a body. +_METHODS_NOT_EXPECTING_BODY = {"GET", "HEAD", "DELETE", "TRACE", "OPTIONS", "CONNECT"} + + +def make_headers( + keep_alive: bool | None = None, + accept_encoding: bool | list[str] | str | None = None, + user_agent: str | None = None, + basic_auth: str | None = None, + proxy_basic_auth: str | None = None, + disable_cache: bool | None = None, +) -> dict[str, str]: + """ + 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'. If the dependencies for + Brotli (either the ``brotli`` or ``brotlicffi`` package) and/or + Zstandard (the ``backports.zstd`` package for Python before 3.14) + algorithms are installed, then their encodings are + included in the string ('br' and 'zstd', respectively). + 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: + + .. code-block:: python + + from pip._vendor import urllib3 + + print(urllib3.util.make_headers(keep_alive=True, user_agent="Batman/1.0")) + # {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + print(urllib3.util.make_headers(accept_encoding=True)) + # {'accept-encoding': 'gzip,deflate'} + """ + headers: dict[str, str] = {} + 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"] = ( + f"Basic {b64encode(basic_auth.encode('latin-1')).decode()}" + ) + + if proxy_basic_auth: + headers["proxy-authorization"] = ( + f"Basic {b64encode(proxy_basic_auth.encode('latin-1')).decode()}" + ) + + if disable_cache: + headers["cache-control"] = "no-cache" + + return headers + + +def set_file_position( + body: typing.Any, pos: _TYPE_BODY_POSITION | None +) -> _TYPE_BODY_POSITION | None: + """ + 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 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: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) -> None: + """ + 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, int): + try: + body_seek(body_pos) + except OSError as e: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) from e + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) + else: + raise ValueError( + f"body_pos must be of type integer, instead it was {type(body_pos)}." + ) + + +class ChunksAndContentLength(typing.NamedTuple): + chunks: typing.Iterable[bytes] | None + content_length: int | None + + +def body_to_chunks( + body: typing.Any | None, method: str, blocksize: int +) -> ChunksAndContentLength: + """Takes the HTTP request method, body, and blocksize and + transforms them into an iterable of chunks to pass to + socket.sendall() and an optional 'Content-Length' header. + + A 'Content-Length' of 'None' indicates the length of the body + can't be determined so should use 'Transfer-Encoding: chunked' + for framing instead. + """ + + chunks: typing.Iterable[bytes] | None + content_length: int | None + + # No body, we need to make a recommendation on 'Content-Length' + # based on whether that request method is expected to have + # a body or not. + if body is None: + chunks = None + if method.upper() not in _METHODS_NOT_EXPECTING_BODY: + content_length = 0 + else: + content_length = None + + # Bytes or strings become bytes + elif isinstance(body, (str, bytes)): + chunks = (to_bytes(body),) + content_length = len(chunks[0]) + + # File-like object, TODO: use seek() and tell() for length? + elif hasattr(body, "read"): + + def chunk_readable() -> typing.Iterable[bytes]: + encode = isinstance(body, io.TextIOBase) + while True: + datablock = body.read(blocksize) + if not datablock: + break + if encode: + datablock = datablock.encode("utf-8") + yield datablock + + chunks = chunk_readable() + content_length = None + + # Otherwise we need to start checking via duck-typing. + else: + try: + # Check if the body implements the buffer API. + mv = memoryview(body) + except TypeError: + try: + # Check if the body is an iterable + chunks = iter(body) + content_length = None + except TypeError: + raise TypeError( + f"'body' must be a bytes-like object, file-like " + f"object, or iterable. Instead was {body!r}" + ) from None + else: + # Since it implements the buffer API can be passed directly to socket.sendall() + chunks = (body,) + content_length = mv.nbytes + + return ChunksAndContentLength(chunks=chunks, content_length=content_length) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/response.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/response.py new file mode 100644 index 0000000000000000000000000000000000000000..0f4578696fa2e17a900c6890ec26d65e860b0b72 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/response.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import http.client as httplib +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect + +from ..exceptions import HeaderParsingError + + +def is_fp_closed(obj: object) -> bool: + """ + 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() # type: ignore[no-any-return, attr-defined] + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed # type: ignore[no-any-return, attr-defined] + 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 # type: ignore[attr-defined] + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers: httplib.HTTPMessage) -> None: + """ + 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(f"expected httplib.Message, got {type(headers)}.") + + unparsed_data = None + + # 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 = headers.get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + + # 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 headers.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: httplib.HTTPResponse) -> bool: + """ + Checks whether the request of a response has been a HEAD-request. + + :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_str = response._method # type: str # type: ignore[attr-defined] + return method_str.upper() == "HEAD" diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/retry.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/retry.py new file mode 100644 index 0000000000000000000000000000000000000000..7649898e1d9930724a456c1c6fdecb66e078b4cc --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/retry.py @@ -0,0 +1,557 @@ +from __future__ import annotations + +import email +import logging +import random +import re +import time +import typing +from itertools import takewhile +from types import TracebackType + +from ..exceptions import ( + ConnectTimeoutError, + InvalidHeader, + MaxRetryError, + ProtocolError, + ProxyError, + ReadTimeoutError, + ResponseError, +) +from .util import reraise + +if typing.TYPE_CHECKING: + from typing_extensions import Self + + from ..connectionpool import ConnectionPool + from ..response import BaseHTTPResponse + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +class RequestHistory(typing.NamedTuple): + method: str | None + url: str | None + error: Exception | None + status: int | None + redirect_location: str | None + + +class Retry: + """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: + + .. code-block:: python + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request("GET", "https://example.com/") + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request("GET", "https://example.com/", retries=Retry(10)) + + Retries can be disabled by passing ``False``: + + .. code-block:: python + + response = http.request("GET", "https://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 Collection 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 ``None`` value to retry on any verb. + + :param Collection 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 previous retries})) + + seconds. If `backoff_jitter` is non-zero, this sleep is extended by:: + + random.uniform(0, {backoff jitter}) + + seconds. For example, if the backoff_factor is 0.1, then :func:`Retry.sleep` will + sleep for [0.0s, 0.2s, 0.4s, 0.8s, ...] between retries. No backoff will ever + be longer than `backoff_max`. + + By default, backoff is disabled (factor set to 0). + + :param float backoff_max: + The maximum backoff time (in seconds) between retry attempts. + This value caps the computed backoff from `backoff_factor`. + + :param float backoff_jitter: + Random jitter amount (in seconds) added to the computed backoff. + Jitter is sampled uniformly from `0` to `backoff_jitter`. + + :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 Collection 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. + + :param int retry_after_max: Number of seconds to allow as the maximum for + Retry-After headers. Defaults to :attr:`Retry.DEFAULT_RETRY_AFTER_MAX`. + Any Retry-After headers larger than this value will be limited to this + value. + """ + + #: 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( + ["Cookie", "Authorization", "Proxy-Authorization"] + ) + + #: Default maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 + + # This is undocumented in the RFC. Setting to 6 hours matches other popular libraries. + #: Default maximum allowed value for Retry-After headers in seconds + DEFAULT_RETRY_AFTER_MAX: typing.Final[int] = 21600 + + # Backward compatibility; assigned outside of the class. + DEFAULT: typing.ClassVar[Retry] + + def __init__( + self, + total: bool | int | None = 10, + connect: int | None = None, + read: int | None = None, + redirect: bool | int | None = None, + status: int | None = None, + other: int | None = None, + allowed_methods: typing.Collection[str] | None = DEFAULT_ALLOWED_METHODS, + status_forcelist: typing.Collection[int] | None = None, + backoff_factor: float = 0, + backoff_max: float = DEFAULT_BACKOFF_MAX, + raise_on_redirect: bool = True, + raise_on_status: bool = True, + history: tuple[RequestHistory, ...] | None = None, + respect_retry_after_header: bool = True, + remove_headers_on_redirect: typing.Collection[ + str + ] = DEFAULT_REMOVE_HEADERS_ON_REDIRECT, + backoff_jitter: float = 0.0, + retry_after_max: int = DEFAULT_RETRY_AFTER_MAX, + ) -> None: + 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.backoff_max = backoff_max + self.retry_after_max = retry_after_max + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or () + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = frozenset( + h.lower() for h in remove_headers_on_redirect + ) + self.backoff_jitter = backoff_jitter + + def new(self, **kw: typing.Any) -> Self: + params = dict( + total=self.total, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, + allowed_methods=self.allowed_methods, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + backoff_max=self.backoff_max, + retry_after_max=self.retry_after_max, + 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, + backoff_jitter=self.backoff_jitter, + ) + + params.update(kw) + return type(self)(**params) # type: ignore[arg-type] + + @classmethod + def from_int( + cls, + retries: Retry | bool | int | None, + redirect: bool | int | None = True, + default: Retry | bool | int | None = None, + ) -> Retry: + """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) -> float: + """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)) + if self.backoff_jitter != 0.0: + backoff_value += random.random() * self.backoff_jitter + return float(max(0, min(self.backoff_max, backoff_value))) + + def parse_retry_after(self, retry_after: str) -> float: + seconds: float + # 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(f"Invalid Retry-After header: {retry_after}") + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + seconds = max(seconds, 0) + + # Check the seconds do not exceed the specified maximum + if seconds > self.retry_after_max: + seconds = self.retry_after_max + + return seconds + + def get_retry_after(self, response: BaseHTTPResponse) -> float | None: + """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: BaseHTTPResponse) -> bool: + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self) -> None: + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response: BaseHTTPResponse | None = None) -> 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: Exception) -> bool: + """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: Exception) -> bool: + """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: str) -> bool: + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods + """ + if self.allowed_methods and method.upper() not in self.allowed_methods: + return False + return True + + def is_retry( + self, method: str, status_code: int, has_retry_after: bool = False + ) -> bool: + """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 bool( + 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) -> bool: + """Are we out of retries?""" + retry_counts = [ + x + for x in ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + if x + ] + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment( + self, + method: str | None = None, + url: str | None = None, + response: BaseHTTPResponse | None = None, + error: Exception | None = None, + _pool: ConnectionPool | None = None, + _stacktrace: TracebackType | None = None, + ) -> Self: + """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.BaseHTTPResponse` + :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 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 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 method is None or not self._is_method_retryable(method): + raise 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" + response_redirect_location = response.get_redirect_location() + if response_redirect_location: + redirect_location = response_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(): + reason = error or ResponseError(cause) + raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type] + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self) -> str: + return ( + f"{type(self).__name__}(total={self.total}, connect={self.connect}, " + f"read={self.read}, redirect={self.redirect}, status={self.status})" + ) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_.py new file mode 100644 index 0000000000000000000000000000000000000000..e66549a76c4b5821e639e5facfeb63dd1a39d543 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_.py @@ -0,0 +1,477 @@ +from __future__ import annotations + +import hashlib +import hmac +import os +import socket +import sys +import typing +import warnings +from binascii import unhexlify + +from ..exceptions import ProxySchemeUnsupported, SSLError +from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE + +SSLContext = None +SSLTransport = None +HAS_NEVER_CHECK_COMMON_NAME = False +IS_PYOPENSSL = False +ALPN_PROTOCOLS = ["http/1.1"] + +_TYPE_VERSION_INFO = tuple[int, int, int, str, int] + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = { + length: getattr(hashlib, algorithm, None) + for length, algorithm in ((32, "md5"), (40, "sha1"), (64, "sha256")) +} + + +def _is_has_never_check_common_name_reliable( + openssl_version: str, +) -> bool: + # As of May 2023, all released versions of LibreSSL fail to reject certificates with + # only common names, see https://github.com/urllib3/urllib3/pull/3024 + is_openssl = openssl_version.startswith("OpenSSL ") + + return is_openssl + + +if typing.TYPE_CHECKING: + from ssl import VerifyMode + from typing import TypedDict + + from .ssltransport import SSLTransport as SSLTransportType + + class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False): + subjectAltName: tuple[tuple[str, str], ...] + subject: tuple[tuple[tuple[str, str], ...], ...] + serialNumber: str + + +# Mapping from 'ssl.PROTOCOL_TLSX' to 'TLSVersion.X' +_SSL_VERSION_TO_TLS_VERSION: dict[int, int] = {} + +try: # Do we have ssl at all? + import ssl + from ssl import ( # type: ignore[assignment] + CERT_REQUIRED, + HAS_NEVER_CHECK_COMMON_NAME, + OP_NO_COMPRESSION, + OP_NO_TICKET, + OPENSSL_VERSION, + PROTOCOL_TLS, + PROTOCOL_TLS_CLIENT, + VERIFY_X509_PARTIAL_CHAIN, + VERIFY_X509_STRICT, + OP_NO_SSLv2, + OP_NO_SSLv3, + SSLContext, + TLSVersion, + ) + + PROTOCOL_SSLv23 = PROTOCOL_TLS + + # Setting SSLContext.hostname_checks_common_name = False didn't work with + # LibreSSL, check details in the used function. + if HAS_NEVER_CHECK_COMMON_NAME and not _is_has_never_check_common_name_reliable( + OPENSSL_VERSION, + ): # Defensive: + HAS_NEVER_CHECK_COMMON_NAME = False + + # Need to be careful here in case old TLS versions get + # removed in future 'ssl' module implementations. + for attr in ("TLSv1", "TLSv1_1", "TLSv1_2"): + try: + _SSL_VERSION_TO_TLS_VERSION[getattr(ssl, f"PROTOCOL_{attr}")] = getattr( + TLSVersion, attr + ) + except AttributeError: # Defensive: + continue + + from .ssltransport import SSLTransport # type: ignore[assignment] +except ImportError: + OP_NO_COMPRESSION = 0x20000 # type: ignore[assignment, misc] + OP_NO_TICKET = 0x4000 # type: ignore[assignment, misc] + OP_NO_SSLv2 = 0x1000000 # type: ignore[assignment, misc] + OP_NO_SSLv3 = 0x2000000 # type: ignore[assignment, misc] + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 # type: ignore[assignment, misc] + PROTOCOL_TLS_CLIENT = 16 # type: ignore[assignment, misc] + VERIFY_X509_PARTIAL_CHAIN = 0x80000 # type: ignore[assignment,misc] + VERIFY_X509_STRICT = 0x20 # type: ignore[assignment, misc] + + +_TYPE_PEER_CERT_RET = typing.Union["_TYPE_PEER_CERT_RET_DICT", bytes, None] + + +def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: + """ + 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. + """ + + if cert is None: + raise SSLError("No certificate for the peer.") + + fingerprint = fingerprint.replace(":", "").lower() + digest_length = len(fingerprint) + if digest_length not in HASHFUNC_MAP: + raise SSLError(f"Fingerprint of invalid length: {fingerprint}") + hashfunc = HASHFUNC_MAP.get(digest_length) + if hashfunc is None: + raise SSLError( + f"Hash function implementation unavailable for fingerprint length: {digest_length}" + ) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not hmac.compare_digest(cert_digest, fingerprint_bytes): + raise SSLError( + f'Fingerprints did not match. Expected "{fingerprint}", got "{cert_digest.hex()}"' + ) + + +def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode: + """ + 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 # type: ignore[no-any-return] + + return candidate # type: ignore[return-value] + + +def resolve_ssl_version(candidate: None | int | str) -> int: + """ + 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 typing.cast(int, res) + + return candidate + + +def create_urllib3_context( + ssl_version: int | None = None, + cert_reqs: int | None = None, + options: int | None = None, + ciphers: str | None = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + verify_flags: int | None = None, +) -> ssl.SSLContext: + """Creates and configures an :class:`ssl.SSLContext` instance for use with urllib3. + + :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. + + This parameter is deprecated instead use 'ssl_minimum_version'. + :param ssl_minimum_version: + The minimum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. + :param ssl_maximum_version: + The maximum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. + Not recommended to set to anything other than 'ssl.TLSVersion.MAXIMUM_SUPPORTED' which is the + default value. + :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. Defaults to either system configured + ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers. + :param verify_flags: + The flags for certificate verification operations. These default to + ``ssl.VERIFY_X509_PARTIAL_CHAIN`` and ``ssl.VERIFY_X509_STRICT`` for Python 3.13+. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + if SSLContext is None: + raise TypeError("Can't create an SSLContext object without an ssl module") + + # This means 'ssl_version' was specified as an exact value. + if ssl_version not in (None, PROTOCOL_TLS, PROTOCOL_TLS_CLIENT): + # Disallow setting 'ssl_version' and 'ssl_minimum|maximum_version' + # to avoid conflicts. + if ssl_minimum_version is not None or ssl_maximum_version is not None: + raise ValueError( + "Can't specify both 'ssl_version' and either " + "'ssl_minimum_version' or 'ssl_maximum_version'" + ) + + # 'ssl_version' is deprecated and will be removed in the future. + else: + # Use 'ssl_minimum_version' and 'ssl_maximum_version' instead. + ssl_minimum_version = _SSL_VERSION_TO_TLS_VERSION.get( + ssl_version, TLSVersion.MINIMUM_SUPPORTED + ) + ssl_maximum_version = _SSL_VERSION_TO_TLS_VERSION.get( + ssl_version, TLSVersion.MAXIMUM_SUPPORTED + ) + + # This warning message is pushing users to use 'ssl_minimum_version' + # instead of both min/max. Best practice is to only set the minimum version and + # keep the maximum version to be it's default value: 'TLSVersion.MAXIMUM_SUPPORTED' + warnings.warn( + "'ssl_version' option is deprecated and will be " + "removed in urllib3 v3.0. Instead use 'ssl_minimum_version'", + category=FutureWarning, + stacklevel=2, + ) + + context = SSLContext(PROTOCOL_TLS_CLIENT) + if ssl_minimum_version is not None: + context.minimum_version = ssl_minimum_version + else: # pyOpenSSL defaults to 'MINIMUM_SUPPORTED' so explicitly set TLSv1.2 here + context.minimum_version = TLSVersion.TLSv1_2 + + if ssl_maximum_version is not None: + context.maximum_version = ssl_maximum_version + + # Unless we're given ciphers defer to either system ciphers in + # the case of OpenSSL 1.1.1+ or use our own secure default ciphers. + if ciphers: + context.set_ciphers(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 + + if verify_flags is None: + verify_flags = 0 + # In Python 3.13+ ssl.create_default_context() sets VERIFY_X509_PARTIAL_CHAIN + # and VERIFY_X509_STRICT so we do the same + if sys.version_info >= (3, 13): + verify_flags |= VERIFY_X509_PARTIAL_CHAIN + verify_flags |= VERIFY_X509_STRICT + + context.verify_flags |= verify_flags + + # 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 when using + # an SSLContext created by pyOpenSSL. + if getattr(context, "post_handshake_auth", None) is not None: + context.post_handshake_auth = True + + # 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. + # We always set 'check_hostname=False' for pyOpenSSL so we rely on our own + # 'ssl.match_hostname()' implementation. + if cert_reqs == ssl.CERT_REQUIRED and not IS_PYOPENSSL: + context.verify_mode = cert_reqs + context.check_hostname = True + else: + context.check_hostname = False + context.verify_mode = cert_reqs + + context.hostname_checks_common_name = False + + if "SSLKEYLOGFILE" in os.environ: + sslkeylogfile = os.path.expandvars(os.environ.get("SSLKEYLOGFILE")) + else: + sslkeylogfile = None + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + + return context + + +@typing.overload +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = ..., + certfile: str | None = ..., + cert_reqs: int | None = ..., + ca_certs: str | None = ..., + server_hostname: str | None = ..., + ssl_version: int | None = ..., + ciphers: str | None = ..., + ssl_context: ssl.SSLContext | None = ..., + ca_cert_dir: str | None = ..., + key_password: str | None = ..., + ca_cert_data: None | str | bytes = ..., + tls_in_tls: typing.Literal[False] = ..., +) -> ssl.SSLSocket: ... + + +@typing.overload +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = ..., + certfile: str | None = ..., + cert_reqs: int | None = ..., + ca_certs: str | None = ..., + server_hostname: str | None = ..., + ssl_version: int | None = ..., + ciphers: str | None = ..., + ssl_context: ssl.SSLContext | None = ..., + ca_cert_dir: str | None = ..., + key_password: str | None = ..., + ca_cert_data: None | str | bytes = ..., + tls_in_tls: bool = ..., +) -> ssl.SSLSocket | SSLTransportType: ... + + +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = None, + certfile: str | None = None, + cert_reqs: int | None = None, + ca_certs: str | None = None, + server_hostname: str | None = None, + ssl_version: int | None = None, + ciphers: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_cert_dir: str | None = None, + key_password: str | None = None, + ca_cert_data: None | str | bytes = None, + tls_in_tls: bool = False, +) -> ssl.SSLSocket | SSLTransportType: + """ + All arguments except for server_hostname, ssl_context, tls_in_tls, ca_cert_data and + ca_cert_dir have the same meaning as they do when using + :func:`ssl.create_default_context`, :meth:`ssl.SSLContext.load_cert_chain`, + :meth:`ssl.SSLContext.set_ciphers` and :meth:`ssl.SSLContext.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 only used in tests. + # 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 OSError as e: + raise SSLError(e) from e + + elif ssl_context is None and hasattr(context, "load_default_certs"): + # try to load OS default certs; works well on Windows. + 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) + + context.set_alpn_protocols(ALPN_PROTOCOLS) + + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname) + return ssl_sock + + +def is_ipaddress(hostname: str | bytes) -> bool: + """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 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: str) -> bool: + """Detects if a key file is encrypted or not.""" + with open(key_file) 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: socket.socket, + ssl_context: ssl.SSLContext, + tls_in_tls: bool, + server_hostname: str | None = None, +) -> ssl.SSLSocket | SSLTransportType: + 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) + + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py new file mode 100644 index 0000000000000000000000000000000000000000..94994f25ae1570211c0b98353790f669ef4585fa --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py @@ -0,0 +1,153 @@ +"""The match_hostname() function from Python 3.5, 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 +# It is modified to remove commonName support. + +from __future__ import annotations + +import ipaddress +import re +import typing +from ipaddress import IPv4Address, IPv6Address + +if typing.TYPE_CHECKING: + from .ssl_ import _TYPE_PEER_CERT_RET_DICT + +__version__ = "3.5.0.1" + + +class CertificateError(ValueError): + pass + + +def _dnsname_match( + dn: typing.Any, hostname: str, max_wildcards: int = 1 +) -> typing.Match[str] | None | bool: + """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 bool(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 _ipaddress_match(ipname: str, host_ip: IPv4Address | IPv6Address) -> bool: + """Exact matching of IP addresses. + + RFC 9110 section 4.3.5: "A reference identity of IP-ID contains the decoded + bytes of the IP address. An IP version 4 address is 4 octets, and an IP + version 6 address is 16 octets. [...] A reference identity of type IP-ID + matches if the address is identical to an iPAddress value of the + subjectAltName extension of the certificate." + """ + # 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(ipname.rstrip()) + return bool(ip.packed == host_ip.packed) + + +def match_hostname( + cert: _TYPE_PEER_CERT_RET_DICT | None, + hostname: str, + hostname_checks_common_name: bool = False, +) -> None: + """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: + host_ip = ipaddress.ip_address(hostname) + except ValueError: + # Not an IP address (common case) + host_ip = None + dnsnames = [] + san: tuple[tuple[str, str], ...] = cert.get("subjectAltName", ()) + key: str + value: str + 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) + + # We only check 'commonName' if it's enabled and we're not verifying + # an IP address. IP addresses aren't valid within 'commonName'. + if hostname_checks_common_name and host_ip is None and not dnsnames: + for sub in cert.get("subject", ()): + for key, value in sub: + if key == "commonName": + if _dnsname_match(value, hostname): + return + dnsnames.append( + value + ) # Defensive: for older PyPy and OpenSSL versions + + 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(f"hostname {hostname!r} doesn't match {dnsnames[0]!r}") + else: + raise CertificateError("no appropriate subjectAltName fields were found") diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssltransport.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssltransport.py new file mode 100644 index 0000000000000000000000000000000000000000..6d59bc3bce2489c3a0aa5bcb83b737dcf33c033b --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssltransport.py @@ -0,0 +1,271 @@ +from __future__ import annotations + +import io +import socket +import ssl +import typing + +from ..exceptions import ProxySchemeUnsupported + +if typing.TYPE_CHECKING: + from typing_extensions import Self + + from .ssl_ import _TYPE_PEER_CERT_RET, _TYPE_PEER_CERT_RET_DICT + + +_WriteBuffer = typing.Union[bytearray, memoryview] +_ReturnValue = typing.TypeVar("_ReturnValue") + +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: ssl.SSLContext) -> None: + """ + 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"): + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, + socket: socket.socket, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + suppress_ragged_eofs: bool = True, + ) -> None: + """ + 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) -> Self: + return self + + def __exit__(self, *_: typing.Any) -> None: + self.close() + + def fileno(self) -> int: + return self.socket.fileno() + + def read(self, len: int = 1024, buffer: typing.Any | None = None) -> int | bytes: + return self._wrap_ssl_read(len, buffer) + + def recv(self, buflen: int = 1024, flags: int = 0) -> int | bytes: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(buflen) + + def recv_into( + self, + buffer: _WriteBuffer, + nbytes: int | None = None, + flags: int = 0, + ) -> None | int | bytes: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if nbytes is None: + nbytes = len(buffer) + return self.read(nbytes, buffer) + + def sendall(self, data: bytes, flags: int = 0) -> None: + 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: bytes, flags: int = 0) -> int: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + return self._ssl_io_loop(self.sslobj.write, data) + + def makefile( + self, + mode: str, + buffering: int | None = None, + *, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> typing.BinaryIO | typing.TextIO | socket.SocketIO: + """ + 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(f"invalid mode {mode!r} (only r, w, b allowed)") + + 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) # type: ignore[arg-type] + self.socket._io_refs += 1 # type: ignore[attr-defined] + 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 + buffer: typing.BinaryIO + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) # type: ignore[assignment] + 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 # type: ignore[misc] + return text + + def unwrap(self) -> None: + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self) -> None: + self.socket.close() + + @typing.overload + def getpeercert( + self, binary_form: typing.Literal[False] = ... + ) -> _TYPE_PEER_CERT_RET_DICT | None: ... + + @typing.overload + def getpeercert(self, binary_form: typing.Literal[True]) -> bytes | None: ... + + def getpeercert(self, binary_form: bool = False) -> _TYPE_PEER_CERT_RET: + return self.sslobj.getpeercert(binary_form) # type: ignore[return-value] + + def version(self) -> str | None: + return self.sslobj.version() + + def cipher(self) -> tuple[str, str, int] | None: + return self.sslobj.cipher() + + def selected_alpn_protocol(self) -> str | None: + return self.sslobj.selected_alpn_protocol() + + def shared_ciphers(self) -> list[tuple[str, str, int]] | None: + return self.sslobj.shared_ciphers() + + def compression(self) -> str | None: + return self.sslobj.compression() + + def settimeout(self, value: float | None) -> None: + self.socket.settimeout(value) + + def gettimeout(self) -> float | None: + return self.socket.gettimeout() + + def _decref_socketios(self) -> None: + self.socket._decref_socketios() # type: ignore[attr-defined] + + def _wrap_ssl_read(self, len: int, buffer: bytearray | None = None) -> int | bytes: + 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 + + # func is sslobj.do_handshake or sslobj.unwrap + @typing.overload + def _ssl_io_loop(self, func: typing.Callable[[], None]) -> None: ... + + # func is sslobj.write, arg1 is data + @typing.overload + def _ssl_io_loop(self, func: typing.Callable[[bytes], int], arg1: bytes) -> int: ... + + # func is sslobj.read, arg1 is len, arg2 is buffer + @typing.overload + def _ssl_io_loop( + self, + func: typing.Callable[[int, bytearray | None], bytes], + arg1: int, + arg2: bytearray | None, + ) -> bytes: ... + + def _ssl_io_loop( + self, + func: typing.Callable[..., _ReturnValue], + arg1: None | bytes | int = None, + arg2: bytearray | None = None, + ) -> _ReturnValue: + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + if arg1 is None and arg2 is None: + ret = func() + elif arg2 is None: + ret = func(arg1) + else: + ret = func(arg1, arg2) + 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 typing.cast(_ReturnValue, ret) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/timeout.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/timeout.py new file mode 100644 index 0000000000000000000000000000000000000000..c6cdb352aae3202d194afdbd0750093f2eaf7aa6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/timeout.py @@ -0,0 +1,275 @@ +from __future__ import annotations + +import time +import typing +from enum import Enum +from socket import getdefaulttimeout + +from ..exceptions import TimeoutStateError + +if typing.TYPE_CHECKING: + from typing import Final + + +class _TYPE_DEFAULT(Enum): + # This value should never be passed to socket.settimeout() so for safety we use a -1. + # socket.settimout() raises a ValueError for negative values. + token = -1 + + +_DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token + +_TYPE_TIMEOUT = typing.Optional[typing.Union[float, _TYPE_DEFAULT]] + + +class Timeout: + """Timeout configuration. + + Timeouts can be defined as a default for a pool: + + .. code-block:: python + + from pip._vendor import urllib3 + + timeout = urllib3.util.Timeout(connect=2.0, read=7.0) + + http = urllib3.PoolManager(timeout=timeout) + + resp = http.request("GET", "https://example.com/") + + print(resp.status) + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request("GET", "https://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", "https://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. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT + + def __init__( + self, + total: _TYPE_TIMEOUT = None, + connect: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + read: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + ) -> None: + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") + self._start_connect: float | None = None + + def __repr__(self) -> str: + return f"{type(self).__name__}(connect={self._connect!r}, read={self._read!r}, total={self.total!r})" + + # __str__ provided for backwards compatibility + __str__ = __repr__ + + @staticmethod + def resolve_default_timeout(timeout: _TYPE_TIMEOUT) -> float | None: + return getdefaulttimeout() if timeout is _DEFAULT_TIMEOUT else timeout + + @classmethod + def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: + """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 None or value is _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) + ) from None + + 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: + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) from None + + return value + + @classmethod + def from_float(cls, timeout: _TYPE_TIMEOUT) -> 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, :attr:`urllib3.util.Timeout.DEFAULT_TIMEOUT`, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self) -> Timeout: + """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) -> float: + """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 = time.monotonic() + return self._start_connect + + def get_connect_duration(self) -> float: + """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 time.monotonic() - self._start_connect + + @property + def connect_timeout(self) -> _TYPE_TIMEOUT: + """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 _DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) # type: ignore[type-var] + + @property + def read_timeout(self) -> float | None: + """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 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 _DEFAULT_TIMEOUT + and self._read is not None + and self._read is not _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 _DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self.resolve_default_timeout(self._read) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/url.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/url.py new file mode 100644 index 0000000000000000000000000000000000000000..82b174ceba29c7695799a5838635c3af31601286 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/url.py @@ -0,0 +1,469 @@ +from __future__ import annotations + +import re +import typing + +from ..exceptions import LocationParseError +from .util import to_str + +# 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( + typing.NamedTuple( + "Url", + [ + ("scheme", typing.Optional[str]), + ("auth", typing.Optional[str]), + ("host", typing.Optional[str]), + ("port", typing.Optional[int]), + ("path", typing.Optional[str]), + ("query", typing.Optional[str]), + ("fragment", typing.Optional[str]), + ], + ) +): + """ + 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. + """ + + def __new__( # type: ignore[no-untyped-def] + cls, + scheme: str | None = None, + auth: str | None = None, + host: str | None = None, + port: int | None = None, + path: str | None = None, + query: str | None = None, + fragment: str | None = None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: + scheme = scheme.lower() + return super().__new__(cls, scheme, auth, host, port, path, query, fragment) + + @property + def hostname(self) -> str | None: + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self) -> str: + """Absolute path including the query string.""" + uri = self.path or "/" + + if self.query is not None: + uri += "?" + self.query + + return uri + + @property + def authority(self) -> str | None: + """ + Authority component as defined in RFC 3986 3.2. + This includes userinfo (auth), host and port. + + i.e. + userinfo@host:port + """ + userinfo = self.auth + netloc = self.netloc + if netloc is None or userinfo is None: + return netloc + else: + return f"{userinfo}@{netloc}" + + @property + def netloc(self) -> str | None: + """ + Network location including host and port. + + If you need the equivalent of urllib.parse's ``netloc``, + use the ``authority`` property instead. + """ + if self.host is None: + return None + if self.port: + return f"{self.host}:{self.port}" + return self.host + + @property + def url(self) -> str: + """ + 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: + + .. code-block:: python + + from pip._vendor import urllib3 + + U = urllib3.util.parse_url("https://google.com/mail/") + + print(U.url) + # "https://google.com/mail/" + + print( urllib3.util.Url("https", "username:password", + "host.com", 80, "/path", "query", "fragment" + ).url + ) + # "https://username:password@host.com:80/path?query#fragment" + """ + scheme, auth, host, port, path, query, fragment = self + url = "" + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + "://" + if auth is not None: + url += auth + "@" + if host is not None: + url += host + if port is not None: + url += ":" + str(port) + if path is not None: + url += path + if query is not None: + url += "?" + query + if fragment is not None: + url += "#" + fragment + + return url + + def __str__(self) -> str: + return self.url + + +@typing.overload +def _encode_invalid_chars( + component: str, allowed_chars: typing.Container[str] +) -> str: # Abstract + ... + + +@typing.overload +def _encode_invalid_chars( + component: None, allowed_chars: typing.Container[str] +) -> None: # Abstract + ... + + +def _encode_invalid_chars( + component: str | None, allowed_chars: typing.Container[str] +) -> str | None: + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = to_str(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 + 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() + + +def _remove_path_dot_segments(path: str) -> str: + # 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 + if 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) + + +@typing.overload +def _normalize_host(host: None, scheme: str | None) -> None: ... + + +@typing.overload +def _normalize_host(host: str, scheme: str | None) -> str: ... + + +def _normalize_host(host: str | None, scheme: str | None) -> str | None: + if 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 f"{host[:start].lower()}%{zone_id}{host[end:]}" + else: + return host.lower() + elif not _IPV4_RE.match(host): + return to_str( + b".".join([_idna_encode(label) for label in host.split(".")]), + "ascii", + ) + return host + + +def _idna_encode(name: str) -> bytes: + if not name.isascii(): + try: + from pip._vendor import idna + except ImportError: + raise LocationParseError( + "Unable to parse URL without the 'idna' module" + ) from None + + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + raise LocationParseError( + f"Name '{name}' is not a valid IDNA label" + ) from None + + return name.lower().encode("ascii") + + +def _encode_target(target: str) -> str: + """Percent-encodes a request target so that there are no invalid characters + + Pre-condition for this function is that 'target' must start with '/'. + If that is the case then _TARGET_RE will always produce a match. + """ + match = _TARGET_RE.match(target) + if not match: # Defensive: + raise LocationParseError(f"{target!r} is not a valid request URI") + + path, query = match.groups() + encoded_target = _encode_invalid_chars(path, _PATH_CHARS) + if query is not None: + query = _encode_invalid_chars(query, _QUERY_CHARS) + encoded_target += "?" + query + return encoded_target + + +def parse_url(url: str) -> 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:`urllib.parse`. + + Example: + + .. code-block:: python + + from pip._vendor import urllib3 + + print( urllib3.util.parse_url('http://google.com/mail/')) + # Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + + print( urllib3.util.parse_url('google.com:80')) + # Url(scheme=None, host='google.com', port=80, path=None, ...) + + print( urllib3.util.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 + + scheme: str | None + authority: str | None + auth: str | None + host: str | None + port: str | None + port_int: int | None + path: str | None + query: str | None + fragment: str | None + + try: + scheme, authority, path, query, fragment = _URI_RE.match(url).groups() # type: ignore[union-attr] + 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() # type: ignore[union-attr] + 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 = int(port) + if not (0 <= port_int <= 65535): + raise LocationParseError(url) + else: + port_int = None + + 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) as e: + raise LocationParseError(source_url) from e + + # 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 + + return Url( + scheme=scheme, + auth=auth, + host=host, + port=port_int, + path=path, + query=query, + fragment=fragment, + ) diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/util.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/util.py new file mode 100644 index 0000000000000000000000000000000000000000..35c77e4025842f548565334a3c04cba90f9283d6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/util.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import typing +from types import TracebackType + + +def to_bytes( + x: str | bytes, encoding: str | None = None, errors: str | None = None +) -> bytes: + if isinstance(x, bytes): + return x + elif not isinstance(x, str): + raise TypeError(f"not expecting type {type(x).__name__}") + if encoding or errors: + return x.encode(encoding or "utf-8", errors=errors or "strict") + return x.encode() + + +def to_str( + x: str | bytes, encoding: str | None = None, errors: str | None = None +) -> str: + if isinstance(x, str): + return x + elif not isinstance(x, bytes): + raise TypeError(f"not expecting type {type(x).__name__}") + if encoding or errors: + return x.decode(encoding or "utf-8", errors=errors or "strict") + return x.decode() + + +def reraise( + tp: type[BaseException] | None, + value: BaseException, + tb: TracebackType | None = None, +) -> typing.NoReturn: + try: + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None # type: ignore[assignment] + tb = None diff --git a/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/wait.py b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/wait.py new file mode 100644 index 0000000000000000000000000000000000000000..aeca0c7ad5b232eeb1ad9c43d315bd1d74eaed9a --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/urllib3/util/wait.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import select +import socket +from functools import partial + +__all__ = ["wait_for_read", "wait_for_write"] + + +# 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. + + +def select_wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + 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 = fn(timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + 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: float | None) -> list[tuple[int, int]]: + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(do_poll(timeout)) + + +def _have_working_poll() -> bool: + # 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() + poll_obj.poll(0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + # 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 + return wait_for_socket(sock, read, write, timeout) + + +def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool: + """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: socket.socket, timeout: float | None = None) -> bool: + """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/hug/lib/python3.12/site-packages/pip/_vendor/vendor.txt b/hug/lib/python3.12/site-packages/pip/_vendor/vendor.txt new file mode 100644 index 0000000000000000000000000000000000000000..50054f4bab5efb5aaaebcbcfef2e8ced9cc25ef6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/_vendor/vendor.txt @@ -0,0 +1,18 @@ +CacheControl==0.14.4 +distlib==0.4.2 +distro==1.9.0 +msgpack==1.1.2 +packaging==26.2 +platformdirs==4.10.0 +pyproject-hooks==1.2.0 +requests==2.34.2 + certifi==2026.10.19 + idna==3.18 + urllib3==2.7.0 +rich==14.2.0 + pygments==2.20.0 +resolvelib==1.2.1 +setuptools==70.3.0 +tomli==2.4.1 +tomli-w==1.2.0 +truststore==0.10.4 diff --git a/hug/lib/python3.12/site-packages/pip/py.typed b/hug/lib/python3.12/site-packages/pip/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..493b53e4e7a3984ddd49780313bf3bd9901dc1e0 --- /dev/null +++ b/hug/lib/python3.12/site-packages/pip/py.typed @@ -0,0 +1,4 @@ +pip is a command line program. While it is implemented in Python, and so is +available for import, you must not use pip's internal APIs in this way. Typing +information is provided as a convenience only and is not a guarantee. Expect +unannounced changes to the API and types in releases. diff --git a/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/INSTALLER b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/METADATA b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..695f2f5db7213cf8ab28a79485420e3705c0f66b --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/METADATA @@ -0,0 +1,273 @@ +Metadata-Version: 2.4 +Name: polars +Version: 2.1.0 +Summary: Blazingly fast DataFrame library +Author-email: Ritchie Vink +License: Copyright (c) 2025 Ritchie Vink + Copyright (c) 2024 (Some portions) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + + 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. + +Project-URL: Homepage, https://www.pola.rs/ +Project-URL: Documentation, https://docs.pola.rs/api/python/stable/reference/index.html +Project-URL: Repository, https://github.com/pola-rs/polars +Project-URL: Changelog, https://github.com/pola-rs/polars/releases +Keywords: dataframe,arrow,out-of-core +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Science/Research +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 :: Only +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Rust +Classifier: Topic :: Scientific/Engineering +Classifier: Typing :: Typed +Requires-Python: >=3.11 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: polars-runtime-32==2.1.0 +Provides-Extra: rt64 +Requires-Dist: polars-runtime-64==2.1.0; extra == "rt64" +Provides-Extra: rtcompat +Requires-Dist: polars-runtime-compat==2.1.0; extra == "rtcompat" +Provides-Extra: polars-cloud +Requires-Dist: polars_cloud>=0.13.0; extra == "polars-cloud" +Provides-Extra: polars-nanook +Requires-Dist: polars_nanook; extra == "polars-nanook" +Provides-Extra: numpy +Requires-Dist: numpy>=1.16.0; extra == "numpy" +Provides-Extra: pandas +Requires-Dist: pandas; extra == "pandas" +Requires-Dist: polars[pyarrow]; extra == "pandas" +Provides-Extra: pyarrow +Requires-Dist: pyarrow>=7.0.0; extra == "pyarrow" +Provides-Extra: pydantic +Requires-Dist: pydantic; extra == "pydantic" +Provides-Extra: calamine +Requires-Dist: fastexcel>=0.9; extra == "calamine" +Provides-Extra: openpyxl +Requires-Dist: openpyxl>=3.0.0; extra == "openpyxl" +Provides-Extra: xlsx2csv +Requires-Dist: xlsx2csv>=0.8.0; extra == "xlsx2csv" +Provides-Extra: xlsxwriter +Requires-Dist: xlsxwriter; extra == "xlsxwriter" +Provides-Extra: excel +Requires-Dist: polars[calamine,openpyxl,xlsx2csv,xlsxwriter]; extra == "excel" +Provides-Extra: adbc +Requires-Dist: adbc-driver-manager[dbapi]; extra == "adbc" +Requires-Dist: adbc-driver-sqlite[dbapi]; extra == "adbc" +Provides-Extra: connectorx +Requires-Dist: connectorx>=0.3.2; extra == "connectorx" +Provides-Extra: sqlalchemy +Requires-Dist: sqlalchemy; extra == "sqlalchemy" +Requires-Dist: polars[pandas]; extra == "sqlalchemy" +Provides-Extra: database +Requires-Dist: polars[adbc,connectorx,sqlalchemy]; extra == "database" +Provides-Extra: fsspec +Requires-Dist: fsspec; extra == "fsspec" +Provides-Extra: deltalake +Requires-Dist: deltalake!=1.5.*,>=1.0.0; extra == "deltalake" +Provides-Extra: iceberg +Requires-Dist: pyiceberg>=0.12.0; extra == "iceberg" +Requires-Dist: polars-iceberg<1.0.0,>=0.1.0; extra == "iceberg" +Provides-Extra: async +Requires-Dist: gevent; extra == "async" +Provides-Extra: cloudpickle +Requires-Dist: cloudpickle; extra == "cloudpickle" +Provides-Extra: graph +Requires-Dist: matplotlib; extra == "graph" +Provides-Extra: plot +Requires-Dist: altair>=5.4.0; extra == "plot" +Provides-Extra: style +Requires-Dist: great-tables>=0.8.0; extra == "style" +Provides-Extra: timezone +Requires-Dist: tzdata; platform_system == "Windows" and extra == "timezone" +Provides-Extra: gpu +Requires-Dist: cudf-polars-cu12; extra == "gpu" +Provides-Extra: all +Requires-Dist: polars[async,cloudpickle,database,deltalake,excel,fsspec,graph,iceberg,numpy,pandas,plot,pyarrow,pydantic,style,timezone]; extra == "all" +Dynamic: license-file + +

+ + Polars logo + +

+ + + +

+ Documentation: + Python + - + Rust + - + Node.js + - + R + | + Agents: + Skill + - + MCP + | + User guide + | + Discord +

+ +## Polars: Extremely fast Query Engine for DataFrames + +Polars is an analytical query engine for DataFrames, written in Rust. It is designed to be fast, +easy to use and expressive. Key features are: + +- **Fast**: written from the ground up in Rust with multi-threaded, vectorized (SIMD) execution +- **Lazy & eager execution**: with query optimization out of the box +- **Larger-than-RAM**: the streaming engine processes datasets that don't fit in memory +- **Expressive API**: compose complex queries with powerful expressions +- **Extensible**: extend Polars natively with custom code through + [I/O and Expression plugins](https://docs.pola.rs/user-guide/plugins/) +- **Multi-language**: bindings for Python, Rust, Node.js, R, and SQL +- **GPU support**: optionally accelerate queries on NVIDIA GPUs +- **Interoperable**: uses the + [Apache Arrow Columnar Format](https://arrow.apache.org/docs/format/Columnar.html) for zero-copy + data sharing + +To learn more, read the [user guide](https://docs.pola.rs/). + +## Polars in action + +Queries are composed from expressions. This lazy query gets optimized out of the box and runs in +parallel across all available cores: + +```python +import polars as pl + +df = ( + pl.scan_parquet("orders.parquet") + .filter(pl.col("status") == "shipped") + .group_by("customer_id") + .agg( + pl.col("amount").sum().alias("total"), + pl.len().alias("n_orders"), + ) + .sort("total", descending=True) + .collect() +) +``` + +## Performance + +Polars is very fast. In fact, it is one of the best performing Dataframe solutions available. See +the [PDS-H benchmarks](https://www.pola.rs/benchmarks.html) results. + +### Handles larger-than-RAM data + +If you have data that does not fit into memory, Polars' query engine is able to process your query +(or parts of your query) in a streaming fashion. This drastically reduces memory requirements, so +you might be able to process your 250GB dataset on your laptop. Collect with +`collect(engine='streaming')` to run the query streaming. + +## Installation + +### Python + +Install the latest Polars version with: + +```sh +pip install polars +``` + +See the [User Guide](https://docs.pola.rs/user-guide/installation/#feature-flags) for more details +on optional dependencies + +
+Compile Polars from source + +If you want a bleeding edge release you should compile Polars from source. Advanced users can also +compile for maximum performance for their architecture. + +This can be done by going through the following steps in sequence: + +1. Install the latest [Rust compiler](https://www.rust-lang.org/tools/install) +2. Install [maturin](https://maturin.rs/): `pip install maturin` +3. `cd py-polars` and choose one of the following: + - `make build`, slow binary with debug assertions and limited symbols, fast compile times + - `make build-debug`, same as `make build`, but with all symbols, produces large binaries + - `make build-release`, fast binary without debug assertions, minimal debug symbols, long compile + times + - `make build-nodebug-release`, same as build-release but without any debug symbols, slightly + faster to compile + - `make build-debug-release`, same as build-release but with full debug symbols, slightly slower + to compile + - `make build-dist-release`, fastest binary, extreme compile times + +By default the binary is compiled with optimizations turned on for a modern CPU. Specify `LTS_CPU=1` +with the command if your CPU is older and does not support e.g. AVX2. + +Note that the Rust crate implementing the Python bindings is called `py-polars` to distinguish from +the wrapped Rust crate `polars` itself. However, both the Python package and the Python module are +named `polars`, so you can `pip install polars` and `import polars`. + +
+ +Check the [Installation guide](https://docs.pola.rs/user-guide/installation/) for more advanced +installations. For example when you expect more than 2^32 (~4.2 billion) rows, run on an old CPU +(e.g. dating from before 2011), or on an `x86-64` build of Python on Apple Silicon under Rosetta. + +## Contributing + +Want to contribute? Read our [contributing guide](https://docs.pola.rs/development/contributing/) +and check the issue tracker for accepted issues. + +Contributors new to the codebase can look for the `good first issue` label to get familiar with the +project. + +You can [join the Polars Discord server](https://discord.gg/4UfP5cfBE7) for any help along the way. + +## Distributed Polars + +Running into hardware limitations executing your queries? Read how you can +[horizontally scale your Polars query on a cluster](https://docs.pola.rs/polars-cloud/). + +## License + +Polars is licensed under the [MIT License](LICENSE) (SPDX: `MIT`). diff --git a/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/RECORD b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..f2e0157a3dc2822bcf2ba485e9a7321ff5d81df6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/RECORD @@ -0,0 +1,446 @@ +polars-2.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +polars-2.1.0.dist-info/METADATA,sha256=45FgfkOk-3Pr5qWYVKCN07FUgbG8ZyO_xTJ5e7J9tCY,11525 +polars-2.1.0.dist-info/RECORD,, +polars-2.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91 +polars-2.1.0.dist-info/licenses/LICENSE,sha256=gpYCI3Lcg8yO8jSz1Iie-Pixd5dUp4vr8T0sUXimtMQ,1145 +polars-2.1.0.dist-info/top_level.txt,sha256=vIzgVNcxRpbMMJtEjTxm_7TVHy7_v6atr3v-R4pw78k,7 +polars/__init__.py,sha256=xycvU1Px2NgBhL5GB_P1l1784N3YpMHZE1SyqsErEqE,12465 +polars/__pycache__/__init__.cpython-312.pyc,, +polars/__pycache__/_cpu_check.cpython-312.pyc,, +polars/__pycache__/_dependencies.cpython-312.pyc,, +polars/__pycache__/_plr.cpython-312.pyc,, +polars/__pycache__/_reexport.cpython-312.pyc,, +polars/__pycache__/_typing.cpython-312.pyc,, +polars/__pycache__/_warnings.cpython-312.pyc,, +polars/__pycache__/api.cpython-312.pyc,, +polars/__pycache__/config.cpython-312.pyc,, +polars/__pycache__/exceptions.cpython-312.pyc,, +polars/__pycache__/plugins.cpython-312.pyc,, +polars/__pycache__/schema.cpython-312.pyc,, +polars/__pycache__/selectors.cpython-312.pyc,, +polars/__pycache__/string_cache.cpython-312.pyc,, +polars/_cpu_check.py,sha256=VM2vh0VKTIqpXLB3gzm2MyqrUVCdKhLd4OxSxf-EpMs,10143 +polars/_dependencies.py,sha256=wlhm2yADaso2haHulW55phVeBVVxAkuKR89LYeKTk2s,11562 +polars/_plr.py,sha256=Sl-ubRSZ5rrQjqFRP-SPgEGDoANbggmS9r7OnVGyUuo,3191 +polars/_plr.pyi,sha256=P9emD3YijzGzQy1-GNvarkf4e1CFPl-of-ZBngrpVBA,103104 +polars/_reexport.py,sha256=px_h-vm-MWhGbdvaOWsUQS2ryW9Lo7vPTczwEI6eZoY,555 +polars/_typing.py,sha256=WKx1Wr87YFdA9uTkguLlxK963nBvSfDITPvXSvbWBWU,19072 +polars/_utils/__init__.py,sha256=1kQH0ff6GhHER9dq5rl17DgrqkvNmvaNgjfH930PG6I,621 +polars/_utils/__pycache__/__init__.cpython-312.pyc,, +polars/_utils/__pycache__/async_.cpython-312.pyc,, +polars/_utils/__pycache__/cache.cpython-312.pyc,, +polars/_utils/__pycache__/cloud.cpython-312.pyc,, +polars/_utils/__pycache__/constants.cpython-312.pyc,, +polars/_utils/__pycache__/convert.cpython-312.pyc,, +polars/_utils/__pycache__/deprecation.cpython-312.pyc,, +polars/_utils/__pycache__/expired.cpython-312.pyc,, +polars/_utils/__pycache__/getitem.cpython-312.pyc,, +polars/_utils/__pycache__/logging.cpython-312.pyc,, +polars/_utils/__pycache__/monitoring.cpython-312.pyc,, +polars/_utils/__pycache__/nest_asyncio.cpython-312.pyc,, +polars/_utils/__pycache__/parquet.cpython-312.pyc,, +polars/_utils/__pycache__/polars_version.cpython-312.pyc,, +polars/_utils/__pycache__/pycapsule.cpython-312.pyc,, +polars/_utils/__pycache__/reduce_balanced.cpython-312.pyc,, +polars/_utils/__pycache__/serde.cpython-312.pyc,, +polars/_utils/__pycache__/slice.cpython-312.pyc,, +polars/_utils/__pycache__/threading.cpython-312.pyc,, +polars/_utils/__pycache__/udfs.cpython-312.pyc,, +polars/_utils/__pycache__/unstable.cpython-312.pyc,, +polars/_utils/__pycache__/various.cpython-312.pyc,, +polars/_utils/__pycache__/wrap.cpython-312.pyc,, +polars/_utils/async_.py,sha256=bHIoygcJRF7XhNuX9_JbDiTJwtJAUsuL19aHfEuCOxw,3466 +polars/_utils/cache.py,sha256=yBHRTKxXQ5Lg1AtidF8ihKKZhJY9sgm5u7NttkAUEoM,5840 +polars/_utils/cloud.py,sha256=d83VMLJ1dMoilIAMBTBzb9ZjreJEX_PU2DLWll5Yi2o,1102 +polars/_utils/constants.py,sha256=6XUNj4wvZDYG9EwtNbHbneiVnZ7L26S_frlIxgk_Agw,806 +polars/_utils/construction/__init__.py,sha256=27DwOZG90t9Zva2svFI7IycrABmeseMHQ9z2ysfD5Eg,1006 +polars/_utils/construction/__pycache__/__init__.cpython-312.pyc,, +polars/_utils/construction/__pycache__/dataframe.cpython-312.pyc,, +polars/_utils/construction/__pycache__/other.cpython-312.pyc,, +polars/_utils/construction/__pycache__/series.cpython-312.pyc,, +polars/_utils/construction/__pycache__/utils.cpython-312.pyc,, +polars/_utils/construction/dataframe.py,sha256=U1or_vmdzHbtuckPky1BiRDHSfq8VwaWaGp3iXAwKD8,48575 +polars/_utils/construction/other.py,sha256=REJgUBa3VqBIDoBYh_7nCs1CTEdMtadkNk4DOQkGmvQ,2514 +polars/_utils/construction/series.py,sha256=dv6Oo4IA5aajhcZvRa1BWZacqIdVsTYUEiMj3z6uFug,19645 +polars/_utils/construction/utils.py,sha256=8lHzvzOEVLPLdOW86v6rD_CfGzLIgpsR_gfgyv1oi6g,4341 +polars/_utils/convert.py,sha256=F-zfXd8VOy9YZl6SXz4LiQfhUkIIj0LJ-HL6JNi6SPc,6847 +polars/_utils/deprecation.py,sha256=phkBYIZ8dRII1z6_7XAvCTW3TLZrpTqSEm59lkTXVvo,12398 +polars/_utils/expired.py,sha256=A_lLzWzILeASG-HwcOWkQM4f1ZUalR2cx6gjGwmUGJQ,5265 +polars/_utils/getitem.py,sha256=GIbnY2Wk51T1YCu-7Z3_CDNj2O5vwrUjjA3J03rxMK0,15182 +polars/_utils/logging.py,sha256=8CrsMyOHRlVpIljjqub3GhFM6LTzfcxSpFO5GkEjM0o,525 +polars/_utils/monitoring.py,sha256=Fru3a1CTGP13xcLxdit5T-uvJsQm7HxYolm6-YRs_4I,1268 +polars/_utils/nest_asyncio.py,sha256=EwzneNz_LC_gFDDM3YxO4XgF66ssHQWhoQeNDEjSuaY,15057 +polars/_utils/parquet.py,sha256=UYarlCT03_mfzQ1gSz_ZYox8lka5RzlohgWIYu7i_3E,480 +polars/_utils/parse/__init__.py,sha256=uPgKkopiE56et2re3WRbFhM-KLIEzHOWWdST_UbWSuc,409 +polars/_utils/parse/__pycache__/__init__.cpython-312.pyc,, +polars/_utils/parse/__pycache__/expr.cpython-312.pyc,, +polars/_utils/parse/expr.py,sha256=IMWC0OvPPY3bOcH_mpbvK6R937cZS_Qa0s_yaA47P0M,8840 +polars/_utils/polars_version.py,sha256=c5B6SYjmx_jVYwXzYOGCjDeTpbjIVMIAMOZ7jeoih20,829 +polars/_utils/pycapsule.py,sha256=MYisOIqDt9k0DJH0lG6RNzpznaPixLc-BXHeaefL3SQ,1925 +polars/_utils/reduce_balanced.py,sha256=nLUuuBxHz0AJ8V9UtYl9S-MdMZ_lFQL2BQGA6TVarNQ,883 +polars/_utils/serde.py,sha256=IOHkBa01HWgo991zTHxVIMZgAo8CZGbBRHhjc3fs8fg,1826 +polars/_utils/slice.py,sha256=yCLoZFZJYjiUuA3voaEhJCULsNqd5aMGsPamt3HAMSo,8025 +polars/_utils/threading.py,sha256=D_69UH4zgpTTV_PCioue-lw1FYpIHn_9La69u3CslSE,1287 +polars/_utils/udfs.py,sha256=e_tsT5bwStVlSY2ZbeXDCf7xQ-Y34rbLkVvDFE5wTOo,52090 +polars/_utils/unstable.py,sha256=aPG2k8QsNEcI5SjUDdppRrog-TnrrlWv_awBCIObtt4,1647 +polars/_utils/various.py,sha256=4_ftdk1TUfNHQe_49W3e9lQ5kmvZlPOC8v2l7yfQgj0,24943 +polars/_utils/wrap.py,sha256=y5QAlJkIIoguGDIuCHpiihlPmz7ulZ6kPnRyQTTKRJ0,577 +polars/_warnings.py,sha256=9vImV0qaff2fx0hPFWocrw92LICm4k68O-ZFx9Axwnk,1971 +polars/api.py,sha256=yhIRQezoJqBWAvEiIRO3K4Bfdcc5ogVtpJt4e17hmtM,13465 +polars/catalog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +polars/catalog/__pycache__/__init__.cpython-312.pyc,, +polars/catalog/unity/__init__.py,sha256=H930l4QcK_FYZe0pvdhASbHUh3C6psaJRx8u6Bg2uaU,342 +polars/catalog/unity/__pycache__/__init__.cpython-312.pyc,, +polars/catalog/unity/__pycache__/client.cpython-312.pyc,, +polars/catalog/unity/__pycache__/models.cpython-312.pyc,, +polars/catalog/unity/client.py,sha256=Ue3aH472TwhVc7TwiBH4lScO8FzZSsepIMdok9bPxVM,26076 +polars/catalog/unity/models.py,sha256=qd-O5bGm1bqJGw9qxLHnv60idKgHno4utadFGd1FTe0,3667 +polars/config.py,sha256=1R9Mjx1N9NtFa1z48KJ5EV0NR-DJRjTY3YChDYWTn2Q,67292 +polars/convert/__init__.py,sha256=EZTlSYaVTy6wFKTP8s6l3_zLZHm_qp8YYdQ-vFcHQyI,444 +polars/convert/__pycache__/__init__.cpython-312.pyc,, +polars/convert/__pycache__/general.cpython-312.pyc,, +polars/convert/__pycache__/normalize.cpython-312.pyc,, +polars/convert/general.py,sha256=thUR3-AcBRFKU6eo0arFiPUURRr8xX_H4AtiTqNOPto,41636 +polars/convert/normalize.py,sha256=rYI8IChesYNFLvSYKTek_XqrLq-IdnuNuJkiDbYKK0U,9118 +polars/dataframe/__init__.py,sha256=1YJmMU0zAvRO0_HxXO33DhYbEWLTZW3-0PpSmjq2qtQ,77 +polars/dataframe/__pycache__/__init__.cpython-312.pyc,, +polars/dataframe/__pycache__/_html.cpython-312.pyc,, +polars/dataframe/__pycache__/frame.cpython-312.pyc,, +polars/dataframe/__pycache__/group_by.cpython-312.pyc,, +polars/dataframe/__pycache__/plotting.cpython-312.pyc,, +polars/dataframe/_html.py,sha256=KfuvNTMQPAZWLIU20d2SAzmqc3qA8Ez2Q_XjKv5mGmM,6113 +polars/dataframe/frame.py,sha256=fX-8W-vB6stcqSqBRa1ovkvYSwxjQOAsAUtLnCV0-e4,516683 +polars/dataframe/group_by.py,sha256=o49sxjBQP-2D1GLgTuM11GjyaxB9UzS1M_UU7RLifUA,47842 +polars/dataframe/plotting.py,sha256=i9cpueCAA5etA1bzX2VevlzeU9htox_OSQzMu4_bvkU,7974 +polars/datatype_expr/__init__.py,sha256=LTgBgsmhEomqhIOHh-WZ49u3YcRaEjnMmbJ3Sq4ATII,95 +polars/datatype_expr/__pycache__/__init__.cpython-312.pyc,, +polars/datatype_expr/__pycache__/array.cpython-312.pyc,, +polars/datatype_expr/__pycache__/datatype_expr.cpython-312.pyc,, +polars/datatype_expr/__pycache__/list.cpython-312.pyc,, +polars/datatype_expr/__pycache__/struct.cpython-312.pyc,, +polars/datatype_expr/array.py,sha256=5cXqdTFfMX3fmtG3KlEJC5q6goG2LF-igCuv8KCzyn4,1573 +polars/datatype_expr/datatype_expr.py,sha256=eXP8X1vYlHHy_mTLWDNF4t_HzGWwCaY51hK5Sbh0x84,10163 +polars/datatype_expr/list.py,sha256=gvlmxDqAJS3dlvfBTRh3GjRJGFSuQy3Z27167VRig7U,496 +polars/datatype_expr/struct.py,sha256=CZMHPWtcZGdzzbIQHrWOJSOI6jjmfPpwTFGMukLSdpc,2094 +polars/datatypes/__init__.py,sha256=gi9V6MovITTSOtnXuzDzjQDlZ161T8OIsXdDpRbl3-g,2298 +polars/datatypes/__pycache__/__init__.cpython-312.pyc,, +polars/datatypes/__pycache__/_parse.cpython-312.pyc,, +polars/datatypes/__pycache__/_utils.cpython-312.pyc,, +polars/datatypes/__pycache__/classes.cpython-312.pyc,, +polars/datatypes/__pycache__/constants.cpython-312.pyc,, +polars/datatypes/__pycache__/constructor.cpython-312.pyc,, +polars/datatypes/__pycache__/convert.cpython-312.pyc,, +polars/datatypes/__pycache__/extension.cpython-312.pyc,, +polars/datatypes/__pycache__/group.cpython-312.pyc,, +polars/datatypes/_parse.py,sha256=_O-lQIpLjhruzWhnypbPfuLehwFWcn73iZvWAoh4n0A,5583 +polars/datatypes/_utils.py,sha256=TS9MahbSdsUWnpc2XhzJsEFmDdJkAhrmly0V7FlH49o,1702 +polars/datatypes/classes.py,sha256=JFT82jN0G5T7ZqoB4Vr16_DOyra76MLLywlFrN23yFo,45913 +polars/datatypes/constants.py,sha256=VA2k_1gjYwa29usgtACiSSh5jJ1fwHey6Lndo7uu-Vk,308 +polars/datatypes/constructor.py,sha256=kKbCCqKik9aNsHK1Pm8ntcihF8HYqHztozG5pjjQ3Sw,6040 +polars/datatypes/convert.py,sha256=smdMwSxYaXT1Wi8LqpdXeEZcYkHBYqgx11_W3W4JRgY,11212 +polars/datatypes/extension.py,sha256=FyZnwdMp-nnLT6euLsii3tIKat7SkF8pRb-YZQm_CYQ,2409 +polars/datatypes/group.py,sha256=_WrSgOuzxilYgLGlh0afMAF5_E-SA87qMYiHi0bpb8o,3035 +polars/exceptions.py,sha256=08nTRM6VKwEYZ3pcK7chMzNV_8UTW0oD7rtS_qqrhpw,9657 +polars/expr/__init__.py,sha256=16pHGqIv1T69nW5e2AA-sswyFodvMgdf76fNHCNU8kc,111 +polars/expr/__pycache__/__init__.cpython-312.pyc,, +polars/expr/__pycache__/array.cpython-312.pyc,, +polars/expr/__pycache__/binary.cpython-312.pyc,, +polars/expr/__pycache__/categorical.cpython-312.pyc,, +polars/expr/__pycache__/datetime.cpython-312.pyc,, +polars/expr/__pycache__/expr.cpython-312.pyc,, +polars/expr/__pycache__/ext.cpython-312.pyc,, +polars/expr/__pycache__/list.cpython-312.pyc,, +polars/expr/__pycache__/map.cpython-312.pyc,, +polars/expr/__pycache__/meta.cpython-312.pyc,, +polars/expr/__pycache__/name.cpython-312.pyc,, +polars/expr/__pycache__/string.cpython-312.pyc,, +polars/expr/__pycache__/struct.cpython-312.pyc,, +polars/expr/__pycache__/whenthen.cpython-312.pyc,, +polars/expr/array.py,sha256=55NIjSJU7dkH5Taony54C8O7J3QMmClJIg-qNyrJF3w,48065 +polars/expr/binary.py,sha256=x66fDw0OtRkq1hnMhuWRJ8416v5VAkiYKOwMfPl-Y1M,22204 +polars/expr/categorical.py,sha256=HfivNJAWo8QZzXnMPnuGlZHU3XhXvQoigv-CoEde0Qg,12536 +polars/expr/datetime.py,sha256=El3A30W-33PKRHXZQOclW92F7rJqD6W12r6rQkKD2b8,117022 +polars/expr/expr.py,sha256=naZJ-QR-vWmqJZdaleWXHk0kMNzbjApUTWqpnrxrOvQ,477964 +polars/expr/ext.py,sha256=3Ma7ZBj9WIjU-0MgIQkZ2KIHTMbRZU3tDRG9sXu_jX0,1669 +polars/expr/list.py,sha256=evWn7HcrpUm2Uj8zh-15SzmSApE6mx3_uVLJRTopJrY,66667 +polars/expr/map.py,sha256=89LJ-beDiLoPhJ06hu6mu6gfgITZcZ124XeEPRpO99k,7219 +polars/expr/meta.py,sha256=bkF2r-Xw3UrXncRJxf1svVGl15YK0m3l7-0uR5eDxWE,16810 +polars/expr/name.py,sha256=DKEMi8z-0orPu8S9Qsk-lCMMgQHOfhygoconDwu8mFc,17962 +polars/expr/string.py,sha256=UR7VnE1CW-pw8w5b3w7QnFB1WBxwVc0t608lTKSvfys,139419 +polars/expr/struct.py,sha256=_JDtKEjez1u-EXdm_YocCMmviumrehtIpmis4he9RtA,18949 +polars/expr/whenthen.py,sha256=9ypzKBJ1upS2At_syz-ZLcXrhLG7G_fQ795WBdPWrlk,7379 +polars/functions/__init__.py,sha256=sGjH149kdvQHamsx79tAcaL1yL_GR_GLySZ-EltULeY,3742 +polars/functions/__pycache__/__init__.cpython-312.pyc,, +polars/functions/__pycache__/as_datatype.cpython-312.pyc,, +polars/functions/__pycache__/business.cpython-312.pyc,, +polars/functions/__pycache__/col.cpython-312.pyc,, +polars/functions/__pycache__/datatype.cpython-312.pyc,, +polars/functions/__pycache__/eager.cpython-312.pyc,, +polars/functions/__pycache__/escape_regex.cpython-312.pyc,, +polars/functions/__pycache__/lazy.cpython-312.pyc,, +polars/functions/__pycache__/len.cpython-312.pyc,, +polars/functions/__pycache__/lit.cpython-312.pyc,, +polars/functions/__pycache__/random.cpython-312.pyc,, +polars/functions/__pycache__/repeat.cpython-312.pyc,, +polars/functions/__pycache__/whenthen.cpython-312.pyc,, +polars/functions/aggregation/__init__.py,sha256=kK-3PcWpRnzpv7xMo0Hp9Uhd8TVsdb8xgANUVl8maGY,557 +polars/functions/aggregation/__pycache__/__init__.cpython-312.pyc,, +polars/functions/aggregation/__pycache__/horizontal.cpython-312.pyc,, +polars/functions/aggregation/__pycache__/vertical.cpython-312.pyc,, +polars/functions/aggregation/horizontal.py,sha256=GJHMoE9GaVpOnwINEPpKlzfD23cEq4exN3fAM2HLpLA,10675 +polars/functions/aggregation/vertical.py,sha256=8G8bxurQ9EN-ViRf04NOheMcSknEsx5gofvDFcbP3_c,7910 +polars/functions/as_datatype.py,sha256=y8GD2-cJmtIL-0Ox10314SpibAd3njI2sSzZ1dlxGjU,37200 +polars/functions/business.py,sha256=VNZHkIzmD1QZQCO5_YrAcITTOHl072Pi004ZniBm0OI,6208 +polars/functions/col.py,sha256=LsBTb_BpXMUy00E5yyDkh23f9Gypgtky6aAE8628XzI,13672 +polars/functions/datatype.py,sha256=MV9cyp9JpAqgP-ZTknv72QPAvf7njueODRAh9izFLwA,3613 +polars/functions/eager.py,sha256=Ho0lqTpKj4vqH6A3zF6VMY5EGcDvUtq8ybS7DKhn4Wk,44555 +polars/functions/escape_regex.py,sha256=llzKsZulD721aL0AgwfY2oZ2kuG1YA2C-JvGR_GiC9c,806 +polars/functions/lazy.py,sha256=f7XnTcLnBJD3Nez_6Vzi3dluhyBaAnFjP-kKBhOBTX0,81759 +polars/functions/len.py,sha256=m454SkEisV3L1lE7M4yg8JNgq-UIuivEmhwJNNpZ1tM,1823 +polars/functions/lit.py,sha256=DMA-q56uNeDWaqCoM7-fUbk4zGIE9pLLh-0vcCJOaD4,8594 +polars/functions/random.py,sha256=dZhsXKmQXpKAlU7DCiGbkdiBZ52cFfGKANnwM1aLE3o,528 +polars/functions/range/__init__.py,sha256=ZzOVenkqJReesrM1RQ3l89VU-j6oM6pR9zKcGOtnJQw,597 +polars/functions/range/__pycache__/__init__.cpython-312.pyc,, +polars/functions/range/__pycache__/_utils.cpython-312.pyc,, +polars/functions/range/__pycache__/date_range.cpython-312.pyc,, +polars/functions/range/__pycache__/datetime_range.cpython-312.pyc,, +polars/functions/range/__pycache__/int_range.cpython-312.pyc,, +polars/functions/range/__pycache__/linear_space.cpython-312.pyc,, +polars/functions/range/__pycache__/time_range.cpython-312.pyc,, +polars/functions/range/_utils.py,sha256=tfGQE4s3y-r0jEbfk-uJJk6PFH5imyeEsDo0W6daUOg,447 +polars/functions/range/date_range.py,sha256=0Po8sUPqyKPIdh-RIDusn2ZIErzk0nz0TtMPGpkJLPw,9163 +polars/functions/range/datetime_range.py,sha256=_HA2pQYrU8KiFLmKf6j93Spy1YiwxuaGuWoPSskQZ0o,11761 +polars/functions/range/int_range.py,sha256=Aam0-U0lqK1IPUgA4ipBnNi2XhxX9RgTqf40zdNO0t4,8960 +polars/functions/range/linear_space.py,sha256=qrXcH0sdSLpK3Vh8JdagtVkFwg3M3a66jLlGa3MewMs,9588 +polars/functions/range/time_range.py,sha256=bFf0ONR9PzwArAVdV2c7ytBwO9iVRwPu8ha8PBWoc7s,8374 +polars/functions/repeat.py,sha256=1SuLMFgUBHAh5stGMtViDVqvAr9trLl3GZq_qjFu4Bk,6616 +polars/functions/whenthen.py,sha256=PZ-t6z6AD55yhZisQgCHeeEUoyP6_jkqG2CQ_ADowiA,13791 +polars/interchange/__init__.py,sha256=qJi-Rw924bUfz2hJWjtYYY5I4dLci4Yq-GIqcDa-szw,144 +polars/interchange/__pycache__/__init__.cpython-312.pyc,, +polars/interchange/__pycache__/protocol.cpython-312.pyc,, +polars/interchange/protocol.py,sha256=fDAUUr5dMMxEYeZGnGWLd8_RUyNl8RkVnSksDzCEcyg,1705 +polars/io/__init__.py,sha256=Zq9nBYZvhGxyq_UsM4C7I-LyE2bs83B-_I4gFv2mzO8,1951 +polars/io/__pycache__/__init__.cpython-312.pyc,, +polars/io/__pycache__/_expand_paths.cpython-312.pyc,, +polars/io/__pycache__/_utils.cpython-312.pyc,, +polars/io/__pycache__/arrow_c_stream.cpython-312.pyc,, +polars/io/__pycache__/avro.cpython-312.pyc,, +polars/io/__pycache__/clipboard.cpython-312.pyc,, +polars/io/__pycache__/lines.cpython-312.pyc,, +polars/io/__pycache__/ndjson.cpython-312.pyc,, +polars/io/__pycache__/partition.cpython-312.pyc,, +polars/io/__pycache__/plugins.cpython-312.pyc,, +polars/io/_expand_paths.py,sha256=x3ru5mS2EqCBNIJ5Fv7EkaiOpCG51xD7pkDcUcPvGlM,1951 +polars/io/_utils.py,sha256=6J-64WiF07xjwbGmj4TOGXes0Lrs5tmiSfdwDJgvLEg,12175 +polars/io/arrow_c_stream.py,sha256=7aWd0oXNqHvsHVUqgxZXegJBsoLnX-8Nf-STgVx0zUk,3158 +polars/io/avro.py,sha256=9ZWUiLCpJqfvV_XpXrZdHTymYB7-vp7M-iouk3PmZzU,1719 +polars/io/clipboard.py,sha256=MdBnzvRQUA45OyCswkYW54Q3A3EVGlkndqI3mHqTc58,1040 +polars/io/cloud/__init__.py,sha256=F04FwSWQzcY6kesavKpET0FNmAfFGqeWhx87bS6bCjY,444 +polars/io/cloud/__pycache__/__init__.cpython-312.pyc,, +polars/io/cloud/__pycache__/_utils.cpython-312.pyc,, +polars/io/cloud/_utils.py,sha256=Q7k227FUz5jZDnuTRMOrNukcnnc4zgOZYa1HP3Klyq8,2555 +polars/io/cloud/credential_provider/__init__.py,sha256=F04FwSWQzcY6kesavKpET0FNmAfFGqeWhx87bS6bCjY,444 +polars/io/cloud/credential_provider/__pycache__/__init__.cpython-312.pyc,, +polars/io/cloud/credential_provider/__pycache__/_builder.cpython-312.pyc,, +polars/io/cloud/credential_provider/__pycache__/_providers.cpython-312.pyc,, +polars/io/cloud/credential_provider/_builder.py,sha256=zdgmh-halfBguSBwvr6OyfgXzcnPjx8o0yQcUob0qPA,18951 +polars/io/cloud/credential_provider/_providers.py,sha256=e0pxZGw3aK9KvKefmJjwEqM1ahOGMvm94-KGR5FQLug,21199 +polars/io/csv/__init__.py,sha256=D8ODEgyMGaaoOjFURB_Y4pcMhtcTuDQVo2f4HpnxHs0,102 +polars/io/csv/__pycache__/__init__.cpython-312.pyc,, +polars/io/csv/__pycache__/_utils.cpython-312.pyc,, +polars/io/csv/__pycache__/functions.cpython-312.pyc,, +polars/io/csv/_utils.py,sha256=dcSL4FAEUw4sozM69hAUm4V_RZnDu_zhanxqy3aEpQ0,1157 +polars/io/csv/functions.py,sha256=yJWQTwnckhvhh_n2rOYCremd3OWmUWKVDyBspLzKhEc,38092 +polars/io/database/__init__.py,sha256=iHwXZEfTUlEQHiRb4HU9616lyzHq80ac6jo9aUcx2N0,135 +polars/io/database/__pycache__/__init__.cpython-312.pyc,, +polars/io/database/__pycache__/_arrow_registry.cpython-312.pyc,, +polars/io/database/__pycache__/_cursor_proxies.cpython-312.pyc,, +polars/io/database/__pycache__/_executor.cpython-312.pyc,, +polars/io/database/__pycache__/_inference.cpython-312.pyc,, +polars/io/database/__pycache__/_utils.cpython-312.pyc,, +polars/io/database/__pycache__/functions.cpython-312.pyc,, +polars/io/database/_arrow_registry.py,sha256=3uZMslHHNxqrQRXZYaVxuR7m4LqsF_EdffcMxHoSaGE,3459 +polars/io/database/_cursor_proxies.py,sha256=xx7YAzghHH8Y127YSK-EYddwW_AwUbeqFEmk-JBLrNg,6644 +polars/io/database/_executor.py,sha256=D2kt-LY0Fv5yV3xXgC7tNLJU6eNnoQfL0nh7lGaOAW8,26948 +polars/io/database/_inference.py,sha256=ltLj4rCrh5YZc-o7Yumc23T9bSATRTU-Jw-OSuNbvXM,9406 +polars/io/database/_utils.py,sha256=fV2HE2ovg5jgdxx8JzrxAwnjYgXxoA_tcsq3rSswge4,8046 +polars/io/database/functions.py,sha256=mM8eiOO8kr7q1urL9Zc8qv8F3NmU4XqmU9DkA5-Rmx4,22291 +polars/io/delta/__init__.py,sha256=IrkXPyTFZgN4WdM6a3wEjFytHJ2_twpnVjb7_FHMLgM,112 +polars/io/delta/__pycache__/__init__.cpython-312.pyc,, +polars/io/delta/__pycache__/_dataset.cpython-312.pyc,, +polars/io/delta/__pycache__/_utils.cpython-312.pyc,, +polars/io/delta/__pycache__/functions.cpython-312.pyc,, +polars/io/delta/_dataset.py,sha256=LWJgoSTGN8iJyG4ZaWi9DSHbSjx6ajvJi9v6rL9FcMA,15050 +polars/io/delta/_utils.py,sha256=4tZYUW_E9QbIFc6mp16EXqIOHJdu9CRUQM6f_Gcaps0,8360 +polars/io/delta/functions.py,sha256=L2mhDSLwLL-LUE8Jwz2Ooc-vtEAyT4GV3iVHXTd3oM0,12672 +polars/io/external_reader/__init__.py,sha256=yttvqIKh-OLIUQUjkCPwtlSZfEL0ywBMM8IOo0D9Kao,364 +polars/io/external_reader/__pycache__/__init__.cpython-312.pyc,, +polars/io/external_reader/__pycache__/_external_reader.cpython-312.pyc,, +polars/io/external_reader/__pycache__/api.cpython-312.pyc,, +polars/io/external_reader/_external_reader.py,sha256=sqEK3GMH_F6lk_Xk0cyqDrKXQBB1gu2WKWhIt3eHzag,5170 +polars/io/external_reader/api.py,sha256=WzqCMSKifzkqLKwC_GyWJdLtgeqCaXyph5SQaDh-A64,1097 +polars/io/iceberg/__init__.py,sha256=L5sT2QlGCXzX_IX_rpBqBl3oG7fzGNsM07mH1ffZfE0,214 +polars/io/iceberg/__pycache__/__init__.cpython-312.pyc,, +polars/io/iceberg/__pycache__/_cache.cpython-312.pyc,, +polars/io/iceberg/__pycache__/_dataset.cpython-312.pyc,, +polars/io/iceberg/__pycache__/_plugin.cpython-312.pyc,, +polars/io/iceberg/__pycache__/_sink.cpython-312.pyc,, +polars/io/iceberg/__pycache__/_utils.cpython-312.pyc,, +polars/io/iceberg/__pycache__/functions.cpython-312.pyc,, +polars/io/iceberg/_cache.py,sha256=b8WhX3bhekOpZ9X9gw-anenRb4laQF9mcy71xl8D018,19977 +polars/io/iceberg/_dataset.py,sha256=M4abJMcl4zGuk-Jncw-cDV6oRzjX7QCTTEP4LFO2BSs,45437 +polars/io/iceberg/_plugin.py,sha256=M_Iy9Wz08-XzOCtqv20X-bz4h8Dc4-bTsEZaar598oc,18010 +polars/io/iceberg/_sink.py,sha256=pi_2QbXPhh7fVhzU34faobN4512fYhK_ir0bvVWIeLw,52586 +polars/io/iceberg/_utils.py,sha256=L8qNi4_XxgULuI36DNwKx_k4DbYcQaI0sJkPq-xxyWw,54786 +polars/io/iceberg/functions.py,sha256=634uUKNQDKrBzCAk6UubKNH3O-5BR8Bu2MNW1Q2MVLc,12563 +polars/io/ipc/__init__.py,sha256=XmCXIVeJmDbZuysPLU9Agili-z9Kwkk7GJ-w20sppNw,182 +polars/io/ipc/__pycache__/__init__.cpython-312.pyc,, +polars/io/ipc/__pycache__/functions.cpython-312.pyc,, +polars/io/ipc/functions.py,sha256=Q130S-M9B9rNDyjzX4KmuI3tRy31hQR-dh_Zetp9YsI,17292 +polars/io/json/__init__.py,sha256=3mC2atTEDwLx1Vld1NjzYRey37Nwm3CnWCJvh4h4vX4,67 +polars/io/json/__pycache__/__init__.cpython-312.pyc,, +polars/io/json/__pycache__/read.cpython-312.pyc,, +polars/io/json/read.py,sha256=TaztI3wrXYllVDs9iKoYRSkIE0n4FJFdO9A6u9pvgZQ,3719 +polars/io/lance/__init__.py,sha256=OUDfG74Vh-r3Cm-_cIf2oYmk2rOLhkzi_r9_tZK03YE,76 +polars/io/lance/__pycache__/__init__.cpython-312.pyc,, +polars/io/lance/__pycache__/_reader.cpython-312.pyc,, +polars/io/lance/__pycache__/_scan_resolver.cpython-312.pyc,, +polars/io/lance/__pycache__/api.cpython-312.pyc,, +polars/io/lance/_reader.py,sha256=--JGzLeSDOMQXToiy-LEir47DELDvTRaXqbbqFpABk0,12243 +polars/io/lance/_scan_resolver.py,sha256=gu1-5-zuYFug7p9gOVt_EGUSoLAT-7sIdb4iXHbReuI,8887 +polars/io/lance/api.py,sha256=NBweHzvnuDO4XGfb5t3zhm79ypAFHJ_eZEgRil_YcLo,3189 +polars/io/lines.py,sha256=9kKhTiu0IJnwZYjkIEvh62SX1QlaZApwGiKSKGG_rls,7922 +polars/io/ndjson.py,sha256=tgp1Ol1nEH5mrhtXTdJ6ncRuud77DjzwQXZefyWXf4g,13753 +polars/io/parquet/__init__.py,sha256=fj42DpEC53TiW_YU1gfoKnJ2MrqcsImU3sw17ACBgO8,451 +polars/io/parquet/__pycache__/__init__.cpython-312.pyc,, +polars/io/parquet/__pycache__/decryption.cpython-312.pyc,, +polars/io/parquet/__pycache__/encryption.cpython-312.pyc,, +polars/io/parquet/__pycache__/functions.cpython-312.pyc,, +polars/io/parquet/decryption.py,sha256=IA_6ah3hx2YMsdAjT00tfvrVhrEQMg6G1bI1s04qhxs,2513 +polars/io/parquet/encryption.py,sha256=GhqKZ3QHiCP42HzYckDIVR24t7sJ6k-TYCXvZlswyXk,3343 +polars/io/parquet/functions.py,sha256=2ySVyIhwJ4vW3AK0qWLCn9tOzNezH7y2rkZJrfI-t6U,29430 +polars/io/partition.py,sha256=IOumVMelF5kxY2tNrbyIY9-wYObc9qV6J8iRmU-t-dA,6399 +polars/io/plugins.py,sha256=vSraXa0IATYBC8RBDh2c3kaiNw3L0OKay38dm0F4C_k,7709 +polars/io/pyarrow_dataset/__init__.py,sha256=jECWdlzLTxVRzGX0uGiF3PiVEg8vj9x6DKFD3SaE5OY,112 +polars/io/pyarrow_dataset/__pycache__/__init__.cpython-312.pyc,, +polars/io/pyarrow_dataset/__pycache__/anonymous_scan.cpython-312.pyc,, +polars/io/pyarrow_dataset/__pycache__/functions.cpython-312.pyc,, +polars/io/pyarrow_dataset/anonymous_scan.py,sha256=MjSvVcXRQtNvEXkLpvsJsj0DNXoHhI9zLtFelr-SU7o,3934 +polars/io/pyarrow_dataset/functions.py,sha256=m58fDUDKkaUQYVmC7rKZO54YaKtQ6G9bJ7aGOVjmWZw,2696 +polars/io/scan_options/__init__.py,sha256=Cjy8osLmcWCdwj8n9RRxrYsEyPSrK9Vw0uzVB969YxY,102 +polars/io/scan_options/__pycache__/__init__.cpython-312.pyc,, +polars/io/scan_options/__pycache__/_options.cpython-312.pyc,, +polars/io/scan_options/__pycache__/cast_options.cpython-312.pyc,, +polars/io/scan_options/_options.py,sha256=KUbcVFTOg6ROlAo-kJ5Ohdb4v5yzswC2-j2zISBjajE,1884 +polars/io/scan_options/cast_options.py,sha256=b3y34x3FKk2uV89hJo4FxVQCOWVWQFW2Q-VubpkgQ5Y,6125 +polars/io/spreadsheet/__init__.py,sha256=vB-RiA9Zp89bAxTmpytruJ08XpTzF39Ad8HMx3HzPHQ,114 +polars/io/spreadsheet/__pycache__/__init__.cpython-312.pyc,, +polars/io/spreadsheet/__pycache__/_utils.cpython-312.pyc,, +polars/io/spreadsheet/__pycache__/_write_utils.cpython-312.pyc,, +polars/io/spreadsheet/__pycache__/functions.cpython-312.pyc,, +polars/io/spreadsheet/_utils.py,sha256=6iDKJQlQL0jVVqIOtFzPRLQMc5d9rhkUDNy_nBZu2LI,1272 +polars/io/spreadsheet/_write_utils.py,sha256=WE0ho1LztlMoTFpHKv0XquCBHjuq1yXYO39wlWPlECg,23250 +polars/io/spreadsheet/functions.py,sha256=y0ORn7Hlr3jFp6siaj7qrS0BwzLlP8j6osozleD81JI,50225 +polars/lazyframe/__init__.py,sha256=h1-b3ivhs_kVk2YoSJWtwF3V7jcimjRutVfZpZk97FI,515 +polars/lazyframe/__pycache__/__init__.cpython-312.pyc,, +polars/lazyframe/__pycache__/engine.cpython-312.pyc,, +polars/lazyframe/__pycache__/engine_config.cpython-312.pyc,, +polars/lazyframe/__pycache__/engine_remote.cpython-312.pyc,, +polars/lazyframe/__pycache__/frame.cpython-312.pyc,, +polars/lazyframe/__pycache__/group_by.cpython-312.pyc,, +polars/lazyframe/__pycache__/in_process.cpython-312.pyc,, +polars/lazyframe/__pycache__/opt_flags.cpython-312.pyc,, +polars/lazyframe/__pycache__/query_result.cpython-312.pyc,, +polars/lazyframe/__pycache__/sink_plan.cpython-312.pyc,, +polars/lazyframe/engine.py,sha256=GV1YQNkIPWJEJoUl1PcTG-H7oa9-rPJ5uNJ15H8cL0s,27177 +polars/lazyframe/engine_config.py,sha256=6A2pk41Y6NCYKgeHTLwp_UfbnTpR9ytYV1LuIl2rLu4,2863 +polars/lazyframe/engine_remote.py,sha256=DkoqHfK0gSCHAIf8L09e8EYhgatdAx0eNeIb0XBgSnw,14039 +polars/lazyframe/frame.py,sha256=vUkmAfYXIIrczaHngLSpbOvbrlXggUcP40vmhvztRh0,382920 +polars/lazyframe/group_by.py,sha256=AtLsBl-q2n3NjhlPxrLgON_172MZmGESqnUZVr7VOnY,28219 +polars/lazyframe/in_process.py,sha256=jXREOXIvmaKy_8qmh2mV8di2CAf7LSuYT9eG46_aITM,1133 +polars/lazyframe/opt_flags.py,sha256=4UYy5bqRdMnBtnen3caH9EJqlVBh_9p3nE4u_6trkdA,12957 +polars/lazyframe/query_result.py,sha256=EcvK6VIY1GYlmNOHUPeF8I8KEuB7Xv8IWAGoFzUkY6Y,2575 +polars/lazyframe/resolver/__init__.py,sha256=WzM4a2gmVAodbHERRzzcHerraWOazgf5P4SggMkyiLk,207 +polars/lazyframe/resolver/__pycache__/__init__.cpython-312.pyc,, +polars/lazyframe/resolver/__pycache__/_resolver.cpython-312.pyc,, +polars/lazyframe/resolver/_resolver.py,sha256=MdFy7XLu4tyAZVqt5gSUuPhXj0sA6faF0Kat2r0cDOQ,4033 +polars/lazyframe/sink_plan.py,sha256=FbeaB5jAsQE84nROnHHJjBVegEzzBLSbi02iZJPHVQA,11889 +polars/meta/__init__.py,sha256=H2prBI-QqQt3cHBqsRTUyUdB2M1xVns2ZhYH2gfHxjs,410 +polars/meta/__pycache__/__init__.cpython-312.pyc,, +polars/meta/__pycache__/build.cpython-312.pyc,, +polars/meta/__pycache__/index_type.cpython-312.pyc,, +polars/meta/__pycache__/thread_pool.cpython-312.pyc,, +polars/meta/__pycache__/versions.cpython-312.pyc,, +polars/meta/build.py,sha256=_LwcCf-YqqYsgnFeedReEP9WsNpjLVYmjIMZE_1Bhhk,732 +polars/meta/index_type.py,sha256=-ixBjNRbergiIlmoHKRnyPoNdcqKSNiPMh6l7_dIglw,602 +polars/meta/thread_pool.py,sha256=OHUMGCMX9FV97a-Ho0-5ogHJ9-tUcc-3vANuWKxkaAI,884 +polars/meta/versions.py,sha256=MfZ9f2Z_TvZvE-crrwkKh0VSfh2JUUKzgc7XxM7t5YE,4378 +polars/ml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +polars/ml/__pycache__/__init__.cpython-312.pyc,, +polars/ml/__pycache__/torch.cpython-312.pyc,, +polars/ml/__pycache__/utilities.cpython-312.pyc,, +polars/ml/torch.py,sha256=DqhaOLwuOyNl-PtfuhoCH6Tv1P2Oawr1S7oppQR0ETM,6775 +polars/ml/utilities.py,sha256=DHgXxNxme62b3pmlHRm4qCXRSdYMDGo0Vw80iRFiBvU,970 +polars/plugins.py,sha256=2FF9Fdzh-EzZLb3akaLSCfxHkXX19jpEbel8NlWbe28,5405 +polars/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +polars/schema.py,sha256=DM29g3_B43KbAe47bY7J-6SSh-Cb9SnWrwKtBz7Cdng,10111 +polars/selectors.py,sha256=GdsPMKCXl8sPySn5YW20rJY_3O-ZcwMSgNSoeAqO_Ng,102611 +polars/series/__init__.py,sha256=46p-NaYRZgKX7r-trwCiGYGE6kNVI50eJeRrV90a1KI,69 +polars/series/__pycache__/__init__.cpython-312.pyc,, +polars/series/__pycache__/array.cpython-312.pyc,, +polars/series/__pycache__/binary.cpython-312.pyc,, +polars/series/__pycache__/categorical.cpython-312.pyc,, +polars/series/__pycache__/datetime.cpython-312.pyc,, +polars/series/__pycache__/ext.cpython-312.pyc,, +polars/series/__pycache__/list.cpython-312.pyc,, +polars/series/__pycache__/map.cpython-312.pyc,, +polars/series/__pycache__/plotting.cpython-312.pyc,, +polars/series/__pycache__/series.cpython-312.pyc,, +polars/series/__pycache__/string.cpython-312.pyc,, +polars/series/__pycache__/struct.cpython-312.pyc,, +polars/series/__pycache__/utils.cpython-312.pyc,, +polars/series/array.py,sha256=SZJ3O9aEfv75kCNknR7hAHEiL-h-uZDSza5IRDBrclo,25797 +polars/series/binary.py,sha256=GEMOuT3zguStXbdzIfzeOrCcVn6754eZ_p2f3eGBw3g,9771 +polars/series/categorical.py,sha256=qPOqMWrkLkJOjvbWaEHBpmSVTguIg0_7X3AaroAtAtA,6921 +polars/series/datetime.py,sha256=rpEyIWFuzrz2S4iTndFSlmNvd79hEh193b9srrKO8ZI,64662 +polars/series/ext.py,sha256=zsWBrts0n-Ckv9Zg2yHmSxNZt-rMCIf8jvgRY_Vfyvs,1525 +polars/series/list.py,sha256=m486X4IesSbwXKDgufJNl5V7fa2H68PI27on5McmPiM,31327 +polars/series/map.py,sha256=nTDBDOiIhFlo3_tXhNZErH8sBbDKzTTtcr8YrgkpKkM,4379 +polars/series/plotting.py,sha256=uQJv2vnoWKoIOMdTkTzOSVVSeOfLaIkHLOV_ahkqdAk,6592 +polars/series/series.py,sha256=j_9LBKGv6W4xyxm_f-aFSsJmDS3sFX7o_cetF2dbzbQ,300973 +polars/series/string.py,sha256=K7kmUXpFFzSFOtekFDCwUcFme9PpauqPhJsrJFls5ME,71704 +polars/series/struct.py,sha256=R4RbwqJQePp8B6aUHFFYTSGAaCe5lvo_xah2z5Vo8ag,5984 +polars/series/utils.py,sha256=G91Y9b5NRu-W3UhOsXclRk8yW1Vh4p7Kfhq7pwH0cE8,8433 +polars/sql/__init__.py,sha256=Fjy1FPFh4xKovhjUm6lTkkJJs1Dx-2BoUnVUhfdnNHc,123 +polars/sql/__pycache__/__init__.cpython-312.pyc,, +polars/sql/__pycache__/context.cpython-312.pyc,, +polars/sql/__pycache__/functions.cpython-312.pyc,, +polars/sql/context.py,sha256=DmsBXyrDlm6d-fY2yR2mBX69kqYVLeb21V2FboH-B4Q,37782 +polars/sql/functions.py,sha256=B6ghZF6buc6cdvrCTyRCuhBQ1Up8kTsZu-FexAVM_OQ,4637 +polars/string_cache.py,sha256=5k7sM4TyHx0yV2sQUsiTuA-Td9vFkRIuDKLyaxNDl5c,1824 +polars/testing/__init__.py,sha256=NAska9U67mxAJ2d7_p06HupdJ8rIinwq7nL0BBbZn8A,326 +polars/testing/__pycache__/__init__.cpython-312.pyc,, +polars/testing/asserts/__init__.py,sha256=VxZ5FVDVHdq-IaTpeU1F75YdXVdEcnSrLZB9TZFOJyg,402 +polars/testing/asserts/__pycache__/__init__.cpython-312.pyc,, +polars/testing/asserts/__pycache__/frame.cpython-312.pyc,, +polars/testing/asserts/__pycache__/schema.cpython-312.pyc,, +polars/testing/asserts/__pycache__/series.cpython-312.pyc,, +polars/testing/asserts/__pycache__/utils.cpython-312.pyc,, +polars/testing/asserts/frame.py,sha256=Uf23qRyMfiRbHFIbOacGqzHNn_VxAeDiVsY_lH4j7qg,7351 +polars/testing/asserts/schema.py,sha256=MSpWx32CsCN2TAOjV6n_2ktoJ8lSKngqt0ZN2yWIapY,1821 +polars/testing/asserts/series.py,sha256=5k0nGNFe-Jl3OleqohW3OQYdmsKHueXPgkkLePFOD2k,6555 +polars/testing/asserts/utils.py,sha256=jzg8ahqrMgQ3wKVCWhN0ruJZYpGotyPwW3XTqMe_Rds,398 +polars/testing/parametric/__init__.py,sha256=0DMvrbFd5LVVcQjpOJytEc4XxT50-n9TKZ4Tm0OgWO0,629 +polars/testing/parametric/__pycache__/__init__.cpython-312.pyc,, +polars/testing/parametric/__pycache__/profiles.cpython-312.pyc,, +polars/testing/parametric/profiles.py,sha256=fMzDy2QG4qJjYjXtcYb9LiWMEGQg891Kd8QBTbqJ0lw,3675 +polars/testing/parametric/strategies/__init__.py,sha256=qSN0LV5O-44fUfgXb_TVM4OHToXAGb9Py_ONwZk1fmE,342 +polars/testing/parametric/strategies/__pycache__/__init__.cpython-312.pyc,, +polars/testing/parametric/strategies/__pycache__/_utils.cpython-312.pyc,, +polars/testing/parametric/strategies/__pycache__/core.cpython-312.pyc,, +polars/testing/parametric/strategies/__pycache__/data.cpython-312.pyc,, +polars/testing/parametric/strategies/__pycache__/dtype.cpython-312.pyc,, +polars/testing/parametric/strategies/_utils.py,sha256=cen12yEv6UVSlJzDS721_U36CeVWeqPyRbTbzhIoWCA,397 +polars/testing/parametric/strategies/core.py,sha256=M1yB1U6ax4ld4kcFw2yIbJuX4R8FzWxGbWN4Yy8hQCw,18572 +polars/testing/parametric/strategies/data.py,sha256=AXtwbHk30WwrMHS7VZbZcSn0EbUtFwVvSGl3Z7I95qs,15634 +polars/testing/parametric/strategies/dtype.py,sha256=9PGRQ2Faeu2MlvFv5juFzDAoW7OU0XLlcBUwIdjnJ4w,15122 diff --git a/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/WHEEL b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..d31999332c3abbaf2e0d6018b33f2f8e7e29c0bb --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (84.2.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/licenses/LICENSE b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..33b09bbb6c5bd43c5e985e2affed64c5ed093616 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2025 Ritchie Vink +Copyright (c) 2024 (Some portions) NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +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/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/top_level.txt b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..6276040a353aea5c64fd7d4fb0e19b59f8b62679 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars-2.1.0.dist-info/top_level.txt @@ -0,0 +1 @@ +polars diff --git a/hug/lib/python3.12/site-packages/polars/__init__.py b/hug/lib/python3.12/site-packages/polars/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a9fc3edd539a6b99692e38fbd3c29cbc56c23d6d --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/__init__.py @@ -0,0 +1,569 @@ +""" +Polars: Blazingly fast DataFrames +================================= + +Polars is a fast, open-source library for data manipulation with an expressive, typed API. + +Basic usage: + + >>> import polars as pl + >>> df = pl.DataFrame( + ... { + ... "name": ["Alice", "Bob", "Charlie"], + ... "age": [25, 30, 35], + ... "city": ["New York", "London", "Tokyo"], + ... } + ... ) + >>> df.filter(pl.col("age") > 28) + shape: (2, 3) + ┌─────────┬─────┬────────┐ + │ name ┆ age ┆ city │ + │ --- ┆ --- ┆ --- │ + │ str ┆ i64 ┆ str │ + ╞═════════╪═════╪════════╡ + │ Bob ┆ 30 ┆ London │ + │ Charlie ┆ 35 ┆ Tokyo │ + └─────────┴─────┴────────┘ + +User Guide: https://docs.pola.rs/ +Python API Documentation: https://docs.pola.rs/api/python/stable/ +Source Code: https://github.com/pola-rs/polars +""" # noqa: D400, W505, D205 + +import contextlib + +with contextlib.suppress(ImportError): # Module not available when building docs + # We also configure the allocator before importing the Polars Rust bindings. + # See https://github.com/pola-rs/polars/issues/18088, + # https://github.com/pola-rs/polars/pull/21829. + import os + + jemalloc_conf = "dirty_decay_ms:500,muzzy_decay_ms:1000" + if os.environ.get("POLARS_THP") == "1": + jemalloc_conf += ",thp:always,metadata_thp:always" + if override := os.environ.get("_RJEM_MALLOC_CONF"): + jemalloc_conf += "," + override + os.environ["_RJEM_MALLOC_CONF"] = jemalloc_conf + + # Initialize polars on the rust side. This function is highly + # unsafe and should only be called once. + from polars._plr import __register_startup_deps + from polars._warnings import _polars_warn + + __register_startup_deps(_polars_warn) + +from typing import TYPE_CHECKING, Any + +from polars import api, exceptions, plugins, selectors +from polars._utils.polars_version import get_polars_version as _get_polars_version + +# TODO: remove need for importing wrap utils at top level +from polars._utils.wrap import wrap_df, wrap_s # noqa: F401 +from polars.catalog.unity import Catalog +from polars.config import Config +from polars.convert import ( + from_arrow, + from_dataframe, + from_dict, + from_dicts, + from_numpy, + from_pandas, + from_records, + from_repr, + from_torch, + json_normalize, +) +from polars.dataframe import DataFrame +from polars.datatype_expr import DataTypeExpr +from polars.datatypes import ( + Array, + BaseExtension, + Binary, + Boolean, + Categorical, + Categories, + DataType, + Date, + Datetime, + Decimal, + Duration, + Enum, + Extension, + Field, + Float16, + Float32, + Float64, + Int8, + Int16, + Int32, + Int64, + Int128, + List, + Map, + Null, + Object, + String, + Struct, + Time, + UInt8, + UInt16, + UInt32, + UInt64, + UInt128, + Unknown, + Utf8, +) +from polars.datatypes.extension import ( + get_extension_type, + register_extension_type, + unregister_extension_type, +) +from polars.expr import Expr +from polars.functions import ( + align_frames, + all, + all_horizontal, + any, + any_horizontal, + approx_n_unique, + arange, + arctan2, + arg_sort_by, + arg_where, + business_day_count, + coalesce, + col, + collect_all, + collect_all_async, + concat, + concat_arr, + concat_list, + concat_str, + corr, + count, + cov, + cum_count, + cum_fold, + cum_reduce, + cum_sum, + cum_sum_horizontal, + date, + date_range, + date_ranges, + datetime, + datetime_range, + datetime_ranges, + dtype_of, + duration, + element, + escape_regex, + exclude, + explain_all, + field, + first, + fold, + format, + from_epoch, + head, + implode, + int_range, + int_ranges, + last, + len, + linear_space, + linear_spaces, + list, + lit, + map_batches, + map_groups, + max, + max_horizontal, + mean, + mean_horizontal, + median, + merge_sorted, + min, + min_horizontal, + n_unique, + nth, + ones, + pipe_with_dtype, + quantile, + reduce, + repeat, + rolling_corr, + rolling_cov, + row_index, + select, + self_dtype, + set_random_seed, + sql_expr, + std, + struct, + struct_with_fields, + sum, + sum_horizontal, + tail, + time, + time_range, + time_ranges, + union, + var, + when, + zeros, +) +from polars.interchange import CompatLevel +from polars.io import ( + FileProviderArgs, + ParquetDecryptionProperties, + ParquetEncryptionProperties, + PartitionBy, + ScanCastOptions, + defer, + delete_iceberg, + read_avro, + read_clipboard, + read_csv, + read_database, + read_database_uri, + read_delta, + read_excel, + read_ipc, + read_ipc_schema, + read_ipc_stream, + read_json, + read_lines, + read_ndjson, + read_ods, + read_parquet, + read_parquet_metadata, + read_parquet_schema, + scan_arrow_c_stream, + scan_csv, + scan_delta, + scan_external_reader, + scan_iceberg, + scan_ipc, + scan_lance, + scan_lines, + scan_ndjson, + scan_parquet, + scan_pyarrow_dataset, +) +from polars.io.cloud import ( + CredentialProvider, + CredentialProviderAWS, + CredentialProviderAzure, + CredentialProviderFunction, + CredentialProviderFunctionReturn, + CredentialProviderGCP, +) +from polars.lazyframe import ( + Engine, + GPUEngine, + InMemoryEngine, + LazyFrame, + QueryOptFlags, + RemoteEngine, + StreamingEngine, +) +from polars.meta import ( + build_info, + get_index_type, + show_versions, + thread_pool_size, +) +from polars.schema import Schema +from polars.series import Series +from polars.sql import SQLContext, sql +from polars.string_cache import ( + StringCache, + disable_string_cache, + enable_string_cache, + using_string_cache, +) + +__version__: str = _get_polars_version() +del _get_polars_version + +__all__ = [ + # modules + "api", + "exceptions", + "plugins", + "selectors", + # core classes + "DataFrame", + "Expr", + "LazyFrame", + "Series", + # Engine configuration + "Engine", + "GPUEngine", + "InMemoryEngine", + "RemoteEngine", + "StreamingEngine", + # schema + "Schema", + # datatype_expr + "DataTypeExpr", + # datatypes + "Array", + "BaseExtension", + "Binary", + "Boolean", + "Categorical", + "Categories", + "DataType", + "Date", + "Datetime", + "Decimal", + "Duration", + "Enum", + "Extension", + "Field", + "Float16", + "Float32", + "Float64", + "Int8", + "Int16", + "Int32", + "Int64", + "Int128", + "List", + "Map", + "Null", + "Object", + "String", + "Struct", + "Time", + "UInt8", + "UInt16", + "UInt32", + "UInt64", + "UInt128", + "Unknown", + "Utf8", + # datatypes.extension + "register_extension_type", + "unregister_extension_type", + "get_extension_type", + # polars.io + "defer", + "FileProviderArgs", + "ParquetDecryptionProperties", + "ParquetEncryptionProperties", + "PartitionBy", + "ScanCastOptions", + "delete_iceberg", + "read_avro", + "read_clipboard", + "read_csv", + "read_database", + "read_database_uri", + "read_delta", + "read_excel", + "read_ipc", + "read_ipc_schema", + "read_ipc_stream", + "read_json", + "read_lines", + "read_ndjson", + "read_ods", + "read_parquet", + "read_parquet_metadata", + "read_parquet_schema", + "scan_arrow_c_stream", + "scan_csv", + "scan_delta", + "scan_external_reader", + "scan_iceberg", + "scan_ipc", + "scan_lance", + "scan_lines", + "scan_ndjson", + "scan_parquet", + "scan_pyarrow_dataset", + "Catalog", + # polars.io.cloud + "CredentialProvider", + "CredentialProviderAWS", + "CredentialProviderAzure", + "CredentialProviderFunction", + "CredentialProviderFunctionReturn", + "CredentialProviderGCP", + # polars.stringcache + "StringCache", + "disable_string_cache", + "enable_string_cache", + "using_string_cache", + # polars.config + "Config", + # polars.functions.whenthen + "when", + # polars.functions + "align_frames", + "arg_where", + "business_day_count", + "concat", + "union", + "dtype_of", + "struct_with_fields", + "date_range", + "date_ranges", + "datetime_range", + "datetime_ranges", + "element", + "merge_sorted", + "ones", + "repeat", + "self_dtype", + "time_range", + "time_ranges", + "zeros", + "escape_regex", + # polars.functions.aggregation + "all", + "all_horizontal", + "any", + "any_horizontal", + "cum_sum", + "cum_sum_horizontal", + "max", + "max_horizontal", + "mean_horizontal", + "min", + "min_horizontal", + "sum", + "sum_horizontal", + # polars.functions.lazy + "approx_n_unique", + "arange", + "arctan2", + "arg_sort_by", + "coalesce", + "col", + "collect_all", + "collect_all_async", + "concat_arr", + "concat_list", + "concat_str", + "corr", + "count", + "cov", + "cum_count", + "cum_fold", + "cum_reduce", + "date", + "datetime", + "duration", + "exclude", + "explain_all", + "field", + "first", + "fold", + "format", + "from_epoch", + "head", + "implode", + "int_range", + "int_ranges", + "last", + "linear_space", + "linear_spaces", + "lit", + "list", + "map_batches", + "map_groups", + "mean", + "median", + "n_unique", + "nth", + "pipe_with_dtype", + "quantile", + "reduce", + "rolling_corr", + "rolling_cov", + "row_index", + "select", + "std", + "struct", + "tail", + "time", + "var", + # polars.functions.len + "len", + # polars.functions.random + "set_random_seed", + # polars.convert + "from_arrow", + "from_dataframe", + "from_dict", + "from_dicts", + "from_numpy", + "from_pandas", + "from_records", + "from_repr", + "from_torch", + "json_normalize", + # polars.meta + "build_info", + "get_index_type", + "show_versions", + "thread_pool_size", + # polars.sql + "SQLContext", + "sql", + "sql_expr", + "CompatLevel", + # optimization + "QueryOptFlags", +] + + +if not TYPE_CHECKING: + with contextlib.suppress(ImportError): # Module not available when building docs + import polars._plr as plr + + # This causes typechecking to resolve any Polars module attribute + # as Any regardless of existence so we check for TYPE_CHECKING, see #24334. + def __getattr__(name: str) -> Any: + # Backwards compatibility for plugins. This used to be called `polars.polars`, + # but is now `polars._plr`. + if name == "polars": + return plr + elif name == "_allocator": + return plr._allocator + + # Deprecate re-export of exceptions at top-level + if name in dir(exceptions): + msg = ( + f"accessing `{name}` from the top-level `polars` module was deprecated " + "in version 1.0.0. Import it directly from the `polars.exceptions` module " + f"instead, e.g.: `from polars.exceptions import {name}`" + ) + raise exceptions.AttributeRemovedError(msg) + + # Deprecate data type groups at top-level + import polars.datatypes.group as dtgroup + + if name in dir(dtgroup): + msg = ( + f"`{name}` was deprecated in version 1.0.0. Define your own data type groups or " + "use the `polars.selectors` module for selecting columns of a certain data type." + ) + raise exceptions.AttributeRemovedError(msg) + + # Functions removed in 2.0 + removed = { + "arctan2d": "use `arctan2` followed by `.degrees()` instead.", + "groups": "use `df.with_row_index().group_by(...).agg(pl.col('index'))` instead.", + "read_csv_batched": "use `scan_csv` instead, in conjunction with `LazyFrame.collect(engine='streaming')`.", + "threadpool_size": "it was renamed; use `thread_pool_size` instead.", + } + if name in removed: + msg = f"`{name}` was removed in version 2.0; {removed[name]}" + raise exceptions.AttributeRemovedError(msg) + + msg = f"module {__name__!r} has no attribute {name!r}" + raise AttributeError(msg) diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1db88aa30f493f6b1bf945aa8984110e1505bb91 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/_cpu_check.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/_cpu_check.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23d0ce46af45e9180b1ed88e260e1810ab0aa341 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/_cpu_check.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/_dependencies.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/_dependencies.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5df5b38693fe5333729622564452d4747be841ba Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/_dependencies.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/_plr.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/_plr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61b52ad0c5135d3150f82ff8497bf6b5ae449100 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/_plr.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/_reexport.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/_reexport.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a6ca43c775a98db0fa0aec4c71db171c6a8da16 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/_reexport.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/_typing.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/_typing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbc7b4ea605466ae4b168e96ae3e756bed39c065 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/_typing.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/_warnings.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/_warnings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bc1203606904a1b35e4d66f6a3c4c033f02fdfd Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/_warnings.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/api.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bd8350814f7033ba4c3d2258a63a0a69815721a Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/api.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/config.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ce00c0afb149e2c3c747e9844218612ca3d3fa5 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/config.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/exceptions.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ac867707fa901320dbdd6f0ccc46ec4341911a6 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/exceptions.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/plugins.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/plugins.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c3bcfec104396ee472bfd551eea6c3c224b4fbe Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/plugins.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/schema.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/schema.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31d1ffeeac5ec7d6f787a51381a5abcd93b8d802 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/schema.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/selectors.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/selectors.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5877469c247cf5886c3faa94307c271c1927d2bd --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/__pycache__/selectors.cpython-312.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ca0f224f3aefa104a0df16bd8dac5d5837d1b14a7fa50f7f5f51b629edbd8d7 +size 111539 diff --git a/hug/lib/python3.12/site-packages/polars/__pycache__/string_cache.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/__pycache__/string_cache.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3f46e8b0e8f4dc4f0f2591106897d1fea6bcd9a Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/__pycache__/string_cache.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_cpu_check.py b/hug/lib/python3.12/site-packages/polars/_cpu_check.py new file mode 100644 index 0000000000000000000000000000000000000000..fbd5c1a2ac099a5c232d0ccb61ae142f7a907e46 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_cpu_check.py @@ -0,0 +1,286 @@ +# Vendored parts of the code from https://github.com/flababah/cpuid.py, +# so we replicate its copyright license. + +# Copyright (c) 2014 Anders Høst +# +# 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 annotations + +import ctypes +import os +import platform +from ctypes import CFUNCTYPE, POINTER, c_long, c_size_t, c_uint32, c_ulong, c_void_p +from typing import ClassVar + +""" +Determine whether Polars can be run on the current CPU. + +This must be done in pure Python, before the Polars binary is imported. If we +were to try it on the Rust side the compiler could emit illegal instructions +before/during the CPU feature check code. +""" + +_IS_WINDOWS = os.name == "nt" +_IS_64BIT = ctypes.sizeof(ctypes.c_void_p) == 8 +_SUPPORTS_CPUID = platform.machine().lower() in { + "x86_64", + "x64", + "amd64", + "x86", + "i368", + "i686", + "i686-64", +} + + +def get_runtime_repr() -> str: + import polars._plr as plr + + return plr.RUNTIME_REPR + + +def _open_posix_libc() -> ctypes.CDLL: + # Avoid importing ctypes.util if possible. + try: + if os.uname().sysname == "Darwin": + return ctypes.CDLL("libc.dylib", use_errno=True) + else: + return ctypes.CDLL("libc.so.6", use_errno=True) + except Exception: + from ctypes import util as ctutil + + return ctypes.CDLL(ctutil.find_library("c"), use_errno=True) + + +# Posix x86_64: +# Three first call registers : RDI, RSI, RDX +# Volatile registers : RAX, RCX, RDX, RSI, RDI, R8-11 + +# Windows x86_64: +# Three first call registers : RCX, RDX, R8 +# Volatile registers : RAX, RCX, RDX, R8-11 + +# cdecl 32 bit: +# Three first call registers : Stack (%esp) +# Volatile registers : EAX, ECX, EDX + +# fmt: off +_POSIX_64_OPC = [ + 0x53, # push %rbx + 0x89, 0xf0, # mov %esi,%eax + 0x89, 0xd1, # mov %edx,%ecx + 0x0f, 0xa2, # cpuid + 0x89, 0x07, # mov %eax,(%rdi) + 0x89, 0x5f, 0x04, # mov %ebx,0x4(%rdi) + 0x89, 0x4f, 0x08, # mov %ecx,0x8(%rdi) + 0x89, 0x57, 0x0c, # mov %edx,0xc(%rdi) + 0x5b, # pop %rbx + 0xc3 # retq +] + +_WINDOWS_64_OPC = [ + 0x53, # push %rbx + 0x89, 0xd0, # mov %edx,%eax + 0x49, 0x89, 0xc9, # mov %rcx,%r9 + 0x44, 0x89, 0xc1, # mov %r8d,%ecx + 0x0f, 0xa2, # cpuid + 0x41, 0x89, 0x01, # mov %eax,(%r9) + 0x41, 0x89, 0x59, 0x04, # mov %ebx,0x4(%r9) + 0x41, 0x89, 0x49, 0x08, # mov %ecx,0x8(%r9) + 0x41, 0x89, 0x51, 0x0c, # mov %edx,0xc(%r9) + 0x5b, # pop %rbx + 0xc3 # retq +] + +_CDECL_32_OPC = [ + 0x53, # push %ebx + 0x57, # push %edi + 0x8b, 0x7c, 0x24, 0x0c, # mov 0xc(%esp),%edi + 0x8b, 0x44, 0x24, 0x10, # mov 0x10(%esp),%eax + 0x8b, 0x4c, 0x24, 0x14, # mov 0x14(%esp),%ecx + 0x0f, 0xa2, # cpuid + 0x89, 0x07, # mov %eax,(%edi) + 0x89, 0x5f, 0x04, # mov %ebx,0x4(%edi) + 0x89, 0x4f, 0x08, # mov %ecx,0x8(%edi) + 0x89, 0x57, 0x0c, # mov %edx,0xc(%edi) + 0x5f, # pop %edi + 0x5b, # pop %ebx + 0xc3 # ret +] +# fmt: on + +# From memoryapi.h +_MEM_COMMIT = 0x1000 +_MEM_RESERVE = 0x2000 +_MEM_RELEASE = 0x8000 +_PAGE_EXECUTE_READWRITE = 0x40 + + +class CPUID_struct(ctypes.Structure): + _fields_: ClassVar[list[tuple[str, type]]] = [ + (r, c_uint32) for r in ("eax", "ebx", "ecx", "edx") + ] + + +class CPUID: + def __init__(self) -> None: + if _IS_WINDOWS: + if _IS_64BIT: + # VirtualAlloc seems to fail under some weird + # circumstances when ctypes.windll.kernel32 is + # used under 64 bit Python. CDLL fixes this. + self.win = ctypes.CDLL("kernel32.dll") + opc = _WINDOWS_64_OPC + else: + # Here ctypes.windll.kernel32 is needed to get the + # right DLL. Otherwise it will fail when running + # 32 bit Python on 64 bit Windows. + self.win = ctypes.windll.kernel32 # type: ignore[attr-defined] + opc = _CDECL_32_OPC + else: + opc = _POSIX_64_OPC if _IS_64BIT else _CDECL_32_OPC + + size = len(opc) + code = (ctypes.c_ubyte * size)(*opc) + + if _IS_WINDOWS: + self.win.VirtualAlloc.restype = c_void_p + self.win.VirtualAlloc.argtypes = [ + ctypes.c_void_p, + ctypes.c_size_t, + ctypes.c_ulong, + ctypes.c_ulong, + ] + self.addr = self.win.VirtualAlloc( + None, size, _MEM_COMMIT | _MEM_RESERVE, _PAGE_EXECUTE_READWRITE + ) + if not self.addr: + msg = "could not allocate memory for CPUID check" + raise MemoryError(msg) + ctypes.memmove(self.addr, code, size) + else: + import mmap # Only import if necessary. + + # On some platforms PROT_WRITE + PROT_EXEC is forbidden, so we first + # only write and then mprotect into PROT_EXEC. + libc = _open_posix_libc() + mprotect = libc.mprotect + mprotect.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int) + mprotect.restype = ctypes.c_int + + self.mmap = mmap.mmap( + -1, + size, + mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS, + mmap.PROT_READ | mmap.PROT_WRITE, + ) + self.addr = ctypes.addressof(ctypes.c_void_p.from_buffer(self.mmap)) + self.mmap.write(code) + + if mprotect(self.addr, size, mmap.PROT_READ | mmap.PROT_EXEC) != 0: + msg = "could not execute mprotect for CPUID check" + raise RuntimeError(msg) + + func_type = CFUNCTYPE(None, POINTER(CPUID_struct), c_uint32, c_uint32) + self.func_ptr = func_type(self.addr) + + def __call__(self, eax: int, ecx: int = 0) -> CPUID_struct: + struct = CPUID_struct() + self.func_ptr(struct, eax, ecx) + return struct + + def __del__(self) -> None: + if _IS_WINDOWS: + self.win.VirtualFree.restype = c_long + self.win.VirtualFree.argtypes = [c_void_p, c_size_t, c_ulong] + self.win.VirtualFree(self.addr, 0, _MEM_RELEASE) + + +def _read_cpu_flags() -> dict[str, bool]: + if not _SUPPORTS_CPUID: + return {} + + # CPU flags from https://en.wikipedia.org/wiki/CPUID + cpuid = CPUID() + cpuid1 = cpuid(1, 0) + cpuid7 = cpuid(7, 0) + cpuid81h = cpuid(0x80000001, 0) + + return { + "sse3": bool(cpuid1.ecx & (1 << 0)), + "ssse3": bool(cpuid1.ecx & (1 << 9)), + "fma": bool(cpuid1.ecx & (1 << 12)), + "cmpxchg16b": bool(cpuid1.ecx & (1 << 13)), + "sse4.1": bool(cpuid1.ecx & (1 << 19)), + "sse4.2": bool(cpuid1.ecx & (1 << 20)), + "movbe": bool(cpuid1.ecx & (1 << 22)), + "popcnt": bool(cpuid1.ecx & (1 << 23)), + "pclmulqdq": bool(cpuid1.ecx & (1 << 1)), + "avx": bool(cpuid1.ecx & (1 << 28)), + "bmi1": bool(cpuid7.ebx & (1 << 3)), + "bmi2": bool(cpuid7.ebx & (1 << 8)), + "avx2": bool(cpuid7.ebx & (1 << 5)), + "lzcnt": bool(cpuid81h.ecx & (1 << 5)), + } + + +def check_cpu_flags(feature_flags: str) -> None: + expected_cpu_flags = [ + f.lstrip("+") for f in feature_flags.split(",") if not f.startswith("-") + ] + expected_cpu_flags = [ + f + for f in expected_cpu_flags + if f and f != "ctr-static" # Not actually a CPU flag. + ] + + if not expected_cpu_flags or os.environ.get("POLARS_SKIP_CPU_CHECK"): + return + + supported_cpu_flags = _read_cpu_flags() + + missing_features = [] + for f in expected_cpu_flags: + if f not in supported_cpu_flags: + msg = f"unknown feature flag: {f!r}" + raise RuntimeError(msg) + + if not supported_cpu_flags[f]: + missing_features.append(f) + + if missing_features: + import warnings # Only import if necessary. + + warnings.warn( + f"""Missing required CPU features. + +The following required CPU features were not detected: + {", ".join(missing_features)} +Continuing to use this version of Polars on this processor will likely result in a crash. +Install `polars[rtcompat]` instead of `polars` to run Polars with better compatibility. + +Hint: If you are on an Apple ARM machine (e.g. M1) this is likely due to running Python under Rosetta. +It is recommended to install a native version of Python that does not run under Rosetta x86-64 emulation. + +If you believe this warning to be a false positive, you can set the `POLARS_SKIP_CPU_CHECK` environment variable to bypass this check. +""", + RuntimeWarning, + stacklevel=1, + ) diff --git a/hug/lib/python3.12/site-packages/polars/_dependencies.py b/hug/lib/python3.12/site-packages/polars/_dependencies.py new file mode 100644 index 0000000000000000000000000000000000000000..7bb7b28c7a6145f52ba94db34b7ca221568a9da1 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_dependencies.py @@ -0,0 +1,359 @@ +from __future__ import annotations + +import re +import sys +from functools import cache +from importlib import import_module +from importlib.util import find_spec +from types import ModuleType +from typing import TYPE_CHECKING, Any, ClassVar, cast + +if TYPE_CHECKING: + from collections.abc import Hashable + +_ALTAIR_AVAILABLE = True +_DELTALAKE_AVAILABLE = True +_FSSPEC_AVAILABLE = True +_GEVENT_AVAILABLE = True +_GREAT_TABLES_AVAILABLE = True +_HYPOTHESIS_AVAILABLE = True +_NUMPY_AVAILABLE = True +_PANDAS_AVAILABLE = True +_POLARS_CLOUD_AVAILABLE = True +_PYARROW_AVAILABLE = True +_PYDANTIC_AVAILABLE = True +_PYICEBERG_AVAILABLE = True +_TORCH_AVAILABLE = True +_PYTZ_AVAILABLE = True + + +class _LazyModule(ModuleType): + """ + Module that can act both as a lazy-loader and as a proxy. + + Notes + ----- + We do NOT register this module with `sys.modules` so as not to cause + confusion in the global environment. This way we have a valid proxy + module for our own use, but it lives *exclusively* within polars. + """ + + __lazy__ = True + + _mod_pfx: ClassVar[dict[str, str]] = { + "numpy": "np.", + "pandas": "pd.", + "pyarrow": "pa.", + "polars_cloud": "pc.", + } + + def __init__( + self, + module_name: str, + *, + module_available: bool, + ) -> None: + """ + Initialise lazy-loading proxy module. + + Parameters + ---------- + module_name : str + the name of the module to lazy-load (if available). + + module_available : bool + indicate if the referenced module is actually available (we will proxy it + in both cases, but raise a helpful error when invoked if it doesn't exist). + """ + self._module_available = module_available + self._module_name = module_name + self._globals = globals() + super().__init__(module_name) + + def _import(self) -> ModuleType: + # import the referenced module, replacing the proxy in this module's globals + module = import_module(self.__name__) + self._globals[self._module_name] = module + self.__dict__.update(module.__dict__) + return module + + def __getattr__(self, name: str) -> Any: + # have "hasattr('__wrapped__')" return False without triggering import + # (it's for decorators, not modules, but keeps "make doctest" happy) + if name == "__wrapped__": + msg = f"{self._module_name!r} object has no attribute {name!r}" + raise AttributeError(msg) + + # accessing the proxy module's attributes triggers import of the real thing + if self._module_available: + # import the module and return the requested attribute + module = self._import() + return getattr(module, name) + + # user has not installed the proxied/lazy module + elif name == "__name__": + return self._module_name + elif re.match(r"^__\w+__$", name) and name != "__version__": + # allow some minimal introspection on private module + # attrs to avoid unnecessary error-handling elsewhere + return None + else: + # all other attribute access raises a helpful exception + pfx = self._mod_pfx.get(self._module_name, "") + msg = f"{pfx}{name} requires {self._module_name!r} module to be installed" + raise ModuleNotFoundError(msg) from None + + +def _lazy_import(module_name: str) -> tuple[ModuleType, bool]: + """ + Lazy import the given module; avoids up-front import costs. + + Parameters + ---------- + module_name : str + name of the module to import, eg: "pyarrow". + + Notes + ----- + If the requested module is not available (eg: has not been installed), a proxy + module is created in its place, which raises an exception on any attribute + access. This allows for import and use as normal, without requiring explicit + guard conditions - if the module is never used, no exception occurs; if it + is, then a helpful exception is raised. + + Returns + ------- + tuple of (Module, bool) + A lazy-loading module and a boolean indicating if the requested/underlying + module exists (if not, the returned module is a proxy). + """ + # check if module is LOADED + if module_name in sys.modules: + return sys.modules[module_name], True + + # check if module is AVAILABLE + try: + module_spec = find_spec(module_name) + module_available = not (module_spec is None or module_spec.loader is None) + except ModuleNotFoundError: + module_available = False + + # create lazy/proxy module that imports the real one on first use + # (or raises an explanatory ModuleNotFoundError if not available) + return ( + _LazyModule( + module_name=module_name, + module_available=module_available, + ), + module_available, + ) + + +if TYPE_CHECKING: + import dataclasses + import html + import json + import pickle + import subprocess + + import altair + import boto3 + import deltalake + import fsspec + import gevent + import great_tables + import hypothesis + import numpy + import pandas + import polars_cloud + import pyarrow + import pydantic + import pyiceberg + import pyiceberg.schema + import pytz + import torch + +else: + # infrequently-used builtins + dataclasses, _ = _lazy_import("dataclasses") + html, _ = _lazy_import("html") + json, _ = _lazy_import("json") + pickle, _ = _lazy_import("pickle") + subprocess, _ = _lazy_import("subprocess") + + # heavy/optional third party libs + altair, _ALTAIR_AVAILABLE = _lazy_import("altair") + boto3, _BOTO3_AVAILABLE = _lazy_import("boto3") + deltalake, _DELTALAKE_AVAILABLE = _lazy_import("deltalake") + fsspec, _FSSPEC_AVAILABLE = _lazy_import("fsspec") + gevent, _GEVENT_AVAILABLE = _lazy_import("gevent") + great_tables, _GREAT_TABLES_AVAILABLE = _lazy_import("great_tables") + hypothesis, _HYPOTHESIS_AVAILABLE = _lazy_import("hypothesis") + numpy, _NUMPY_AVAILABLE = _lazy_import("numpy") + pandas, _PANDAS_AVAILABLE = _lazy_import("pandas") + polars_cloud, _POLARS_CLOUD_AVAILABLE = _lazy_import("polars_nanook") + if not _POLARS_CLOUD_AVAILABLE: + polars_cloud, _POLARS_CLOUD_AVAILABLE = _lazy_import("polars_cloud") + pyarrow, _PYARROW_AVAILABLE = _lazy_import("pyarrow") + pydantic, _PYDANTIC_AVAILABLE = _lazy_import("pydantic") + pyiceberg, _PYICEBERG_AVAILABLE = _lazy_import("pyiceberg") + torch, _TORCH_AVAILABLE = _lazy_import("torch") + pytz, _PYTZ_AVAILABLE = _lazy_import("pytz") + + +@cache +def _might_be(cls: type, type_: str) -> bool: + # infer whether the given class "might" be associated with the given + # module (in which case it's reasonable to do a real isinstance check; + # we defer that so as not to unnecessarily trigger module import) + try: + return any(f"{type_}." in str(o) for o in cls.mro()) + except TypeError: + return False + + +def _check_for_numpy(obj: Any, *, check_type: bool = True) -> bool: + return _NUMPY_AVAILABLE and _might_be( + cast("Hashable", type(obj) if check_type else obj), "numpy" + ) + + +def _check_for_pandas(obj: Any, *, check_type: bool = True) -> bool: + return _PANDAS_AVAILABLE and _might_be( + cast("Hashable", type(obj) if check_type else obj), "pandas" + ) + + +def _check_for_pyarrow(obj: Any, *, check_type: bool = True) -> bool: + return _PYARROW_AVAILABLE and _might_be( + cast("Hashable", type(obj) if check_type else obj), "pyarrow" + ) + + +def _check_for_pydantic(obj: Any, *, check_type: bool = True) -> bool: + return _PYDANTIC_AVAILABLE and _might_be( + cast("Hashable", type(obj) if check_type else obj), "pydantic" + ) + + +def _check_for_torch(obj: Any, *, check_type: bool = True) -> bool: + return _TORCH_AVAILABLE and _might_be( + cast("Hashable", type(obj) if check_type else obj), "torch" + ) + + +def _check_for_pytz(obj: Any, *, check_type: bool = True) -> bool: + return _PYTZ_AVAILABLE and _might_be( + cast("Hashable", type(obj) if check_type else obj), "pytz" + ) + + +def import_optional( + module_name: str, + err_prefix: str = "required package", + err_suffix: str = "not found", + min_version: str | tuple[int, ...] | None = None, + min_err_prefix: str = "requires", + install_message: str | None = None, +) -> Any: + """ + Import an optional dependency, returning the module. + + Parameters + ---------- + module_name : str + Name of the dependency to import. + err_prefix : str, optional + Error prefix to use in the raised exception (appears before the module name). + err_suffix: str, optional + Error suffix to use in the raised exception (follows the module name). + min_version : {str, tuple[int]}, optional + If a minimum module version is required, specify it here. + min_err_prefix : str, optional + Override the standard "requires" prefix for the minimum version error message. + install_message : str, optional + Override the standard "Please install it using..." exception message fragment. + + Examples + -------- + >>> from polars._dependencies import import_optional + >>> import_optional( + ... "definitely_a_real_module", + ... err_prefix="super-important package", + ... ) # doctest: +SKIP + ImportError: super-important package 'definitely_a_real_module' not installed. + Please install it using the command `pip install definitely_a_real_module`. + """ + from polars._utils.various import parse_version + from polars.exceptions import ModuleUpgradeRequiredError + + module_root = module_name.split(".", 1)[0] + try: + module = import_module(module_name) + except ImportError: + prefix = f"{err_prefix.strip(' ')} " if err_prefix else "" + suffix = f" {err_suffix.strip(' ')}" if err_suffix else "" + err_message = f"{prefix}'{module_name}'{suffix}.\n" + ( + install_message + or f"Please install using the command `pip install {module_root}`." + ) + raise ModuleNotFoundError(err_message) from None + + if min_version: + min_version = parse_version(min_version) + mod_version = parse_version(module.__version__) + if mod_version < min_version: + msg = ( + f"{min_err_prefix} {module_root} " + f"{'.'.join(str(v) for v in min_version)} or higher" + f" (found {'.'.join(str(v) for v in mod_version)})" + ) + raise ModuleUpgradeRequiredError(msg) + + return module + + +__all__ = [ + # lazy-load rarely-used/heavy builtins (for fast startup) + "dataclasses", + "html", + "json", + "pickle", + "subprocess", + # lazy-load third party libs + "altair", + "boto3", + "deltalake", + "fsspec", + "gevent", + "great_tables", + "numpy", + "pandas", + "polars_cloud", + "pydantic", + "pyiceberg", + "pyarrow", + "torch", + "pytz", + # lazy utilities + "_check_for_numpy", + "_check_for_pandas", + "_check_for_pyarrow", + "_check_for_pydantic", + "_check_for_torch", + "_check_for_pytz", + # exported flags/guards + "_ALTAIR_AVAILABLE", + "_DELTALAKE_AVAILABLE", + "_FSSPEC_AVAILABLE", + "_GEVENT_AVAILABLE", + "_GREAT_TABLES_AVAILABLE", + "_HYPOTHESIS_AVAILABLE", + "_NUMPY_AVAILABLE", + "_PANDAS_AVAILABLE", + "_POLARS_CLOUD_AVAILABLE", + "_PYARROW_AVAILABLE", + "_PYDANTIC_AVAILABLE", + "_PYICEBERG_AVAILABLE", + "_TORCH_AVAILABLE", +] diff --git a/hug/lib/python3.12/site-packages/polars/_plr.py b/hug/lib/python3.12/site-packages/polars/_plr.py new file mode 100644 index 0000000000000000000000000000000000000000..556021e6b5647a05a30702dcae973b1cc59a3688 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_plr.py @@ -0,0 +1,102 @@ +# This module represents the Rust API functions exposed to Python through PyO3. We do a +# bit of trickery here to allow overwriting it with other function pointers. + +import builtins +import os +import sys + +from polars._cpu_check import check_cpu_flags + +# example: 1.35.0-beta.1 +PKG_VERSION = "2.1.0" + + +def rt_compat() -> None: + from _polars_runtime_compat import BUILD_FEATURE_FLAGS + + check_cpu_flags(BUILD_FEATURE_FLAGS) + + import _polars_runtime_compat._polars_runtime as plr + + sys.modules[__name__] = plr + + +def rt_64() -> None: + from _polars_runtime_64 import BUILD_FEATURE_FLAGS + + check_cpu_flags(BUILD_FEATURE_FLAGS) + + import _polars_runtime_64._polars_runtime as plr + + sys.modules[__name__] = plr + + +def rt_32() -> None: + from _polars_runtime_32 import BUILD_FEATURE_FLAGS + + check_cpu_flags(BUILD_FEATURE_FLAGS) + + import _polars_runtime_32._polars_runtime as plr + + sys.modules[__name__] = plr + + +if hasattr(builtins, "__POLARS_PLR"): + sys.modules[__name__] = builtins.__POLARS_PLR +else: + # Each of the Polars variants registers a `_polars...` package that we can import + # the PLR from. + + _force = os.environ.get("POLARS_FORCE_PKG") + _prefer = os.environ.get("POLARS_PREFER_PKG") + + pkgs = {"compat": rt_compat, "64": rt_64, "32": rt_32} + default_prefer = [rt_compat, rt_64, rt_32] + + if _force is not None: + try: + pkgs[_force]() + + if sys.modules[__name__].__version__ != PKG_VERSION: + msg = f"Polars Rust module for '{_force}' ({sys.modules[__name__].__version__}) did not match version of Python package '{PKG_VERSION}'" + raise ImportError(msg) + except KeyError: + msg = f"Invalid value for `POLARS_FORCE_PKG` variable: '{_force}'" + raise ValueError(msg) from None + else: + preference = default_prefer + if _prefer is not None: + try: + preference.insert(0, pkgs[_prefer]) + except KeyError: + msg = f"Invalid value for `POLARS_PREFER_PKG` variable: '{_prefer}'" + raise ValueError(msg) from None + + version_warnings = [] + for pkg in preference: + try: + pkg() + + if sys.modules[__name__].__version__ != PKG_VERSION: + import warnings + + version_warnings += [sys.modules[__name__].__version__] + warnings.warn( + f"Skipping Polars' Rust module version '{sys.modules[__name__].__version__}' did not match version of Python package '{PKG_VERSION}'.", + ImportWarning, + stacklevel=2, + ) + continue + + break + except ImportError: + pass + else: + msg = "could not find Polars' Rust module" + if len(version_warnings) > 0: + msg += f". Skipped versions {version_warnings} which don't match Python package version" + raise ImportError(msg) + + +# The version at the top here should match the version specified by the PLR. +assert sys.modules[__name__].__version__ == PKG_VERSION diff --git a/hug/lib/python3.12/site-packages/polars/_plr.pyi b/hug/lib/python3.12/site-packages/polars/_plr.pyi new file mode 100644 index 0000000000000000000000000000000000000000..d703cd3d354511974d5799f7041219d37cdca9b2 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_plr.pyi @@ -0,0 +1,2776 @@ +from builtins import list as pylist +from collections.abc import Callable, Sequence +from typing import Any, Literal, TypeAlias, overload + +from numpy.typing import NDArray + +from polars._typing import ArrowSchemaExportable +from polars.io.iceberg._sink import IcebergSinkState +from polars.io.scan_options._options import ScanOptions +from polars.lazyframe.resolver._resolver import LazyFrameResolver + +# This file mirrors all the definitions made in the polars-python Rust API. + +__version__: str +_BUILD_COMMIT: str +__build__: Any +_ir_nodes: Any +_allocator: Any +_debug: bool +RUNTIME_REPR: str + +CompatLevel: TypeAlias = int | bool +UnicodeForm: TypeAlias = Literal["NFC", "NFKC", "NFD", "NFKD"] +KeyValueMetadata: TypeAlias = Sequence[tuple[str, str]] | Any +TimeZone: TypeAlias = str | None +UpcastOrForbid: TypeAlias = Literal["upcast", "forbid"] +ExtraColumnsPolicy: TypeAlias = Literal["ignore", "raise"] +MissingColumnsPolicy: TypeAlias = Literal["insert", "raise"] +MissingColumnsPolicyOrExpr: TypeAlias = Literal["insert", "raise"] | Any +ColumnMapping: TypeAlias = Any +DeletionFilesList: TypeAlias = Any +DefaultFieldValues: TypeAlias = Any +Path: TypeAlias = str | Any +Schema: TypeAlias = Any +NullValues: TypeAlias = Any +DataType: TypeAlias = Any +SyncOnCloseType: TypeAlias = Literal["none", "data", "all"] +SinkOptions: TypeAlias = dict[str, Any] +SinkTarget: TypeAlias = Any +AsofStrategy: TypeAlias = Literal["backward", "forward", "nearest"] +InterpolationMethod: TypeAlias = Literal["linear", "nearest"] +AvroCompression: TypeAlias = Literal["uncompressed", "snappy", "deflate"] +CategoricalOrdering: TypeAlias = Literal["physical", "lexical"] +StartBy: TypeAlias = Literal[ + "window", + "datapoint", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", +] +ClosedWindow: TypeAlias = Literal["left", "right", "both", "none"] +RoundMode: TypeAlias = Literal["half_to_even", "half_away_from_zero"] +CsvEncoding: TypeAlias = Literal["utf8", "utf8-lossy"] +IpcCompression: TypeAlias = Literal["uncompressed", "lz4", "zstd"] +JoinType: TypeAlias = Literal["inner", "left", "right", "full", "semi", "anti", "cross"] +Label: TypeAlias = Literal["left", "right", "datapoint"] +NonExistent: TypeAlias = Literal["null", "raise"] +NullBehavior: TypeAlias = Literal["drop", "ignore"] +NullStrategy: TypeAlias = Literal["ignore", "propagate"] +ParallelStrategy: TypeAlias = Literal[ + "auto", "columns", "row_groups", "prefiltered", "none" +] +IndexOrder: TypeAlias = Literal["fortran", "c"] +QuantileMethod: TypeAlias = Literal[ + "lower", "higher", "nearest", "linear", "midpoint", "equiprobable" +] +ApproxQuantileMethod: TypeAlias = Literal["auto", "kll", "req_lo", "req_hi", "req_both"] +RankMethod: TypeAlias = Literal["min", "max", "average", "dense", "ordinal", "random"] +Roll: TypeAlias = Literal["raise", "forward", "backward"] +TimeUnit: TypeAlias = Literal["ns", "us", "ms"] +UniqueKeepStrategy: TypeAlias = Literal["first", "last", "any", "none"] +SearchSortedSide: TypeAlias = Literal["any", "left", "right"] +ClosedInterval: TypeAlias = Literal["both", "left", "right", "none"] +WindowMapping: TypeAlias = Literal["group_to_rows", "join", "explode"] +JoinValidation: TypeAlias = Literal["m:m", "m:1", "1:m", "1:1"] +MaintainOrderJoin: TypeAlias = Literal[ + "none", "left", "right", "left_right", "right_left" +] +JoinBuildSide: TypeAlias = Literal[ + "auto", "prefer_left", "prefer_right", "force_left", "force_right" +] +QuoteStyle: TypeAlias = Literal["always", "necessary", "non_numeric", "never"] +SetOperation: TypeAlias = Literal[ + "union", "difference", "intersection", "symmetric_difference" +] +FloatFmt: TypeAlias = Literal["full", "mixed"] +NDArray1D: TypeAlias = NDArray[Any] +StatisticsOptions: TypeAlias = Any +EngineType: TypeAlias = Literal["auto", "in-memory", "streaming", "gpu"] +PyScanOptions: TypeAlias = Any + +# exceptions +class PolarsError(Exception): ... +class ColumnNotFoundError(PolarsError): ... +class ComputeError(PolarsError): ... +class DuplicateError(PolarsError): ... +class InvalidOperationError(PolarsError): ... +class NoDataError(PolarsError): ... +class OutOfBoundsError(PolarsError): ... +class SQLInterfaceError(PolarsError): ... +class SQLSyntaxError(PolarsError): ... +class SchemaError(PolarsError): ... +class SchemaFieldNotFoundError(PolarsError): ... +class ShapeError(PolarsError): ... +class StringCacheMismatchError(PolarsError): ... +class StructFieldNotFoundError(PolarsError): ... +class PolarsWarning(Warning): ... +class PerformanceWarning(PolarsWarning): ... +class CategoricalRemappingWarning(PerformanceWarning): ... +class MapWithoutReturnDtypeWarning(PolarsWarning): ... +class PanicException(PolarsError): ... + +class PySeries: + # map + def map_elements( + self, function: Any, return_dtype: Any | None, skip_nulls: bool + ) -> PySeries: ... + + # general + def struct_unnest(self) -> PyDataFrame: ... + def struct_fields(self) -> pylist[str]: ... + def is_sorted_ascending_flag(self) -> bool: ... + def is_sorted_descending_flag(self) -> bool: ... + def can_fast_explode_flag(self) -> bool: ... + def estimated_size(self) -> int: ... + def get_object(self, index: int) -> Any: ... + def reshape(self, dims: Sequence[int]) -> PySeries: ... + def get_fmt(self, index: int, str_len_limit: int) -> str: ... + def rechunk(self, in_place: bool) -> PySeries | None: ... + def get_index(self, index: int) -> Any: ... + def get_index_signed(self, index: int) -> Any: ... + def bitand(self, other: PySeries) -> PySeries: ... + def bitor(self, other: PySeries) -> PySeries: ... + def bitxor(self, other: PySeries) -> PySeries: ... + def chunk_lengths(self) -> pylist[int]: ... + def name(self) -> str: ... + def rename(self, name: str) -> None: ... + def dtype(self) -> Any: ... + def set_sorted_flag(self, descending: bool) -> PySeries: ... + def n_chunks(self) -> int: ... + def append(self, other: PySeries) -> None: ... + def extend(self, other: PySeries) -> None: ... + def new_from_index(self, index: int, length: int) -> PySeries: ... + def filter(self, filter: PySeries) -> PySeries: ... + def sort( + self, descending: bool, nulls_last: bool, multithreaded: bool + ) -> PySeries: ... + def gather_with_series(self, indices: PySeries) -> PySeries: ... + def null_count(self) -> int: ... + def has_nulls(self) -> bool: ... + def equals( + self, other: PySeries, check_dtypes: bool, check_names: bool, null_equal: bool + ) -> bool: ... + def as_str(self) -> str: ... + def len(self) -> int: ... + def as_single_ptr(self) -> int: ... + def clone(self) -> PySeries: ... + def zip_with(self, mask: PySeries, other: PySeries) -> PySeries: ... + def to_dummies( + self, separator: str | None, drop_first: bool, drop_nulls: bool + ) -> PyDataFrame: ... + def get_list(self, index: int) -> PySeries | None: ... + def n_unique(self) -> int: ... + def floor(self) -> PySeries: ... + def shrink_to_fit(self) -> None: ... + def dot(self, other: PySeries) -> Any: ... + def __getstate__(self) -> bytes: ... + def __setstate__(self, state: bytes) -> None: ... + def skew(self, bias: bool) -> float | None: ... + def kurtosis(self, fisher: bool, bias: bool) -> float | None: ... + def cast(self, dtype: Any, strict: bool, wrap_numerical: bool) -> PySeries: ... + def get_chunks(self) -> pylist[Any]: ... + def is_sorted(self, descending: bool, nulls_last: bool) -> bool: ... + def clear(self) -> PySeries: ... + def head(self, n: int) -> PySeries: ... + def tail(self, n: int) -> PySeries: ... + def value_counts( + self, sort: bool, parallel: bool, name: str, normalize: bool + ) -> PyDataFrame: ... + def slice(self, offset: int, length: int | None) -> PySeries: ... + def not_(self) -> PySeries: ... + def shrink_dtype(self) -> PySeries: ... + def str_to_datetime_infer( + self, + time_unit: TimeUnit | None, + strict: bool, + exact: bool, + ambiguous: PySeries, + ) -> PySeries: ... + def str_to_decimal_infer(self, inference_length: int) -> PySeries: ... + def list_to_struct(self, fields: Sequence[str]) -> PySeries: ... + def str_json_decode(self, infer_schema_length: int | None) -> PySeries: ... + def ext_to(self, dtype: DataType) -> PySeries: ... + def ext_storage(self) -> PySeries: ... + def set(self, mask: PySeries, value: PySeries) -> PySeries: ... + + # aggregations + def any(self, ignore_nulls: bool) -> bool | None: ... + def all(self, ignore_nulls: bool) -> bool | None: ... + def is_empty(self, ignore_nulls: bool) -> bool: ... + def arg_max(self) -> int | None: ... + def arg_min(self) -> int | None: ... + def min(self) -> Any: ... + def max(self) -> Any: ... + def mean(self) -> Any: ... + def median(self) -> Any: ... + def product(self) -> Any: ... + def quantile( + self, quantile: float | Sequence[float], interpolation: QuantileMethod + ) -> Any: ... + def std(self, ddof: int) -> Any: ... + def var(self, ddof: int) -> Any: ... + def sum(self) -> Any: ... + def first(self, ignore_nulls: bool) -> Any: ... + def last(self, ignore_nulls: bool) -> Any: ... + def approx_n_unique(self) -> int: ... + def bitwise_and(self) -> Any: ... + def bitwise_or(self) -> Any: ... + def bitwise_xor(self) -> Any: ... + + # arithmetic + # Operations with another PySeries + def add(self, other: PySeries) -> PySeries: ... + def sub(self, other: PySeries) -> PySeries: ... + def mul(self, other: PySeries) -> PySeries: ... + def div(self, other: PySeries) -> PySeries: ... + def rem(self, other: PySeries) -> PySeries: ... + + # Operations with integer/float/datetime/duration scalars + def add_u8(self, other: int) -> PySeries: ... + def add_u16(self, other: int) -> PySeries: ... + def add_u32(self, other: int) -> PySeries: ... + def add_u64(self, other: int) -> PySeries: ... + def add_i8(self, other: int) -> PySeries: ... + def add_i16(self, other: int) -> PySeries: ... + def add_i32(self, other: int) -> PySeries: ... + def add_i64(self, other: int) -> PySeries: ... + def add_datetime(self, other: int) -> PySeries: ... + def add_duration(self, other: int) -> PySeries: ... + def add_f16(self, other: float) -> PySeries: ... + def add_f32(self, other: float) -> PySeries: ... + def add_f64(self, other: float) -> PySeries: ... + def sub_u8(self, other: int) -> PySeries: ... + def sub_u16(self, other: int) -> PySeries: ... + def sub_u32(self, other: int) -> PySeries: ... + def sub_u64(self, other: int) -> PySeries: ... + def sub_i8(self, other: int) -> PySeries: ... + def sub_i16(self, other: int) -> PySeries: ... + def sub_i32(self, other: int) -> PySeries: ... + def sub_i64(self, other: int) -> PySeries: ... + def sub_datetime(self, other: int) -> PySeries: ... + def sub_duration(self, other: int) -> PySeries: ... + def sub_f16(self, other: float) -> PySeries: ... + def sub_f32(self, other: float) -> PySeries: ... + def sub_f64(self, other: float) -> PySeries: ... + def div_u8(self, other: int) -> PySeries: ... + def div_u16(self, other: int) -> PySeries: ... + def div_u32(self, other: int) -> PySeries: ... + def div_u64(self, other: int) -> PySeries: ... + def div_i8(self, other: int) -> PySeries: ... + def div_i16(self, other: int) -> PySeries: ... + def div_i32(self, other: int) -> PySeries: ... + def div_i64(self, other: int) -> PySeries: ... + def div_f16(self, other: float) -> PySeries: ... + def div_f32(self, other: float) -> PySeries: ... + def div_f64(self, other: float) -> PySeries: ... + def mul_u8(self, other: int) -> PySeries: ... + def mul_u16(self, other: int) -> PySeries: ... + def mul_u32(self, other: int) -> PySeries: ... + def mul_u64(self, other: int) -> PySeries: ... + def mul_i8(self, other: int) -> PySeries: ... + def mul_i16(self, other: int) -> PySeries: ... + def mul_i32(self, other: int) -> PySeries: ... + def mul_i64(self, other: int) -> PySeries: ... + def mul_f16(self, other: float) -> PySeries: ... + def mul_f32(self, other: float) -> PySeries: ... + def mul_f64(self, other: float) -> PySeries: ... + def rem_u8(self, other: int) -> PySeries: ... + def rem_u16(self, other: int) -> PySeries: ... + def rem_u32(self, other: int) -> PySeries: ... + def rem_u64(self, other: int) -> PySeries: ... + def rem_i8(self, other: int) -> PySeries: ... + def rem_i16(self, other: int) -> PySeries: ... + def rem_i32(self, other: int) -> PySeries: ... + def rem_i64(self, other: int) -> PySeries: ... + def rem_f16(self, other: float) -> PySeries: ... + def rem_f32(self, other: float) -> PySeries: ... + def rem_f64(self, other: float) -> PySeries: ... + + # Reverse operations (rhs) + def add_u8_rhs(self, other: int) -> PySeries: ... + def add_u16_rhs(self, other: int) -> PySeries: ... + def add_u32_rhs(self, other: int) -> PySeries: ... + def add_u64_rhs(self, other: int) -> PySeries: ... + def add_i8_rhs(self, other: int) -> PySeries: ... + def add_i16_rhs(self, other: int) -> PySeries: ... + def add_i32_rhs(self, other: int) -> PySeries: ... + def add_i64_rhs(self, other: int) -> PySeries: ... + def add_f16_rhs(self, other: float) -> PySeries: ... + def add_f32_rhs(self, other: float) -> PySeries: ... + def add_f64_rhs(self, other: float) -> PySeries: ... + def sub_u8_rhs(self, other: int) -> PySeries: ... + def sub_u16_rhs(self, other: int) -> PySeries: ... + def sub_u32_rhs(self, other: int) -> PySeries: ... + def sub_u64_rhs(self, other: int) -> PySeries: ... + def sub_i8_rhs(self, other: int) -> PySeries: ... + def sub_i16_rhs(self, other: int) -> PySeries: ... + def sub_i32_rhs(self, other: int) -> PySeries: ... + def sub_i64_rhs(self, other: int) -> PySeries: ... + def sub_f16_rhs(self, other: float) -> PySeries: ... + def sub_f32_rhs(self, other: float) -> PySeries: ... + def sub_f64_rhs(self, other: float) -> PySeries: ... + def div_u8_rhs(self, other: int) -> PySeries: ... + def div_u16_rhs(self, other: int) -> PySeries: ... + def div_u32_rhs(self, other: int) -> PySeries: ... + def div_u64_rhs(self, other: int) -> PySeries: ... + def div_i8_rhs(self, other: int) -> PySeries: ... + def div_i16_rhs(self, other: int) -> PySeries: ... + def div_i32_rhs(self, other: int) -> PySeries: ... + def div_i64_rhs(self, other: int) -> PySeries: ... + def div_f16_rhs(self, other: float) -> PySeries: ... + def div_f32_rhs(self, other: float) -> PySeries: ... + def div_f64_rhs(self, other: float) -> PySeries: ... + def mul_u8_rhs(self, other: int) -> PySeries: ... + def mul_u16_rhs(self, other: int) -> PySeries: ... + def mul_u32_rhs(self, other: int) -> PySeries: ... + def mul_u64_rhs(self, other: int) -> PySeries: ... + def mul_i8_rhs(self, other: int) -> PySeries: ... + def mul_i16_rhs(self, other: int) -> PySeries: ... + def mul_i32_rhs(self, other: int) -> PySeries: ... + def mul_i64_rhs(self, other: int) -> PySeries: ... + def mul_f16_rhs(self, other: float) -> PySeries: ... + def mul_f32_rhs(self, other: float) -> PySeries: ... + def mul_f64_rhs(self, other: float) -> PySeries: ... + def rem_u8_rhs(self, other: int) -> PySeries: ... + def rem_u16_rhs(self, other: int) -> PySeries: ... + def rem_u32_rhs(self, other: int) -> PySeries: ... + def rem_u64_rhs(self, other: int) -> PySeries: ... + def rem_i8_rhs(self, other: int) -> PySeries: ... + def rem_i16_rhs(self, other: int) -> PySeries: ... + def rem_i32_rhs(self, other: int) -> PySeries: ... + def rem_i64_rhs(self, other: int) -> PySeries: ... + def rem_f16_rhs(self, other: float) -> PySeries: ... + def rem_f32_rhs(self, other: float) -> PySeries: ... + def rem_f64_rhs(self, other: float) -> PySeries: ... + + # c_interface + @staticmethod + def _import_arrow_from_c( + name: str, chunks: Sequence[tuple[int, int]] + ) -> PySeries: ... + def _export_arrow_to_c(self, out_ptr: int, out_schema_ptr: int) -> None: ... + + # comparison + # Comparison with another PySeries + def eq(self, rhs: PySeries) -> PySeries: ... + def neq(self, rhs: PySeries) -> PySeries: ... + def gt(self, rhs: PySeries) -> PySeries: ... + def gt_eq(self, rhs: PySeries) -> PySeries: ... + def lt(self, rhs: PySeries) -> PySeries: ... + def lt_eq(self, rhs: PySeries) -> PySeries: ... + + # Comparison with scalar values + def eq_u8(self, rhs: int) -> PySeries: ... + def eq_u16(self, rhs: int) -> PySeries: ... + def eq_u32(self, rhs: int) -> PySeries: ... + def eq_u64(self, rhs: int) -> PySeries: ... + def eq_u128(self, rhs: int) -> PySeries: ... + def eq_i8(self, rhs: int) -> PySeries: ... + def eq_i16(self, rhs: int) -> PySeries: ... + def eq_i32(self, rhs: int) -> PySeries: ... + def eq_i64(self, rhs: int) -> PySeries: ... + def eq_i128(self, rhs: int) -> PySeries: ... + def eq_f16(self, rhs: float) -> PySeries: ... + def eq_f32(self, rhs: float) -> PySeries: ... + def eq_f64(self, rhs: float) -> PySeries: ... + def eq_str(self, rhs: str) -> PySeries: ... + def eq_decimal(self, rhs: Any) -> PySeries: ... + def neq_u8(self, rhs: int) -> PySeries: ... + def neq_u16(self, rhs: int) -> PySeries: ... + def neq_u32(self, rhs: int) -> PySeries: ... + def neq_u64(self, rhs: int) -> PySeries: ... + def neq_u128(self, rhs: int) -> PySeries: ... + def neq_i8(self, rhs: int) -> PySeries: ... + def neq_i16(self, rhs: int) -> PySeries: ... + def neq_i32(self, rhs: int) -> PySeries: ... + def neq_i64(self, rhs: int) -> PySeries: ... + def neq_i128(self, rhs: int) -> PySeries: ... + def neq_f16(self, rhs: float) -> PySeries: ... + def neq_f32(self, rhs: float) -> PySeries: ... + def neq_f64(self, rhs: float) -> PySeries: ... + def neq_str(self, rhs: str) -> PySeries: ... + def neq_decimal(self, rhs: Any) -> PySeries: ... + def gt_u8(self, rhs: int) -> PySeries: ... + def gt_u16(self, rhs: int) -> PySeries: ... + def gt_u32(self, rhs: int) -> PySeries: ... + def gt_u64(self, rhs: int) -> PySeries: ... + def gt_u128(self, rhs: int) -> PySeries: ... + def gt_i8(self, rhs: int) -> PySeries: ... + def gt_i16(self, rhs: int) -> PySeries: ... + def gt_i32(self, rhs: int) -> PySeries: ... + def gt_i64(self, rhs: int) -> PySeries: ... + def gt_i128(self, rhs: int) -> PySeries: ... + def gt_f16(self, rhs: float) -> PySeries: ... + def gt_f32(self, rhs: float) -> PySeries: ... + def gt_f64(self, rhs: float) -> PySeries: ... + def gt_str(self, rhs: str) -> PySeries: ... + def gt_decimal(self, rhs: Any) -> PySeries: ... + def gt_eq_u8(self, rhs: int) -> PySeries: ... + def gt_eq_u16(self, rhs: int) -> PySeries: ... + def gt_eq_u32(self, rhs: int) -> PySeries: ... + def gt_eq_u64(self, rhs: int) -> PySeries: ... + def gt_eq_u128(self, rhs: int) -> PySeries: ... + def gt_eq_i8(self, rhs: int) -> PySeries: ... + def gt_eq_i16(self, rhs: int) -> PySeries: ... + def gt_eq_i32(self, rhs: int) -> PySeries: ... + def gt_eq_i64(self, rhs: int) -> PySeries: ... + def gt_eq_i128(self, rhs: int) -> PySeries: ... + def gt_eq_f16(self, rhs: float) -> PySeries: ... + def gt_eq_f32(self, rhs: float) -> PySeries: ... + def gt_eq_f64(self, rhs: float) -> PySeries: ... + def gt_eq_str(self, rhs: str) -> PySeries: ... + def gt_eq_decimal(self, rhs: Any) -> PySeries: ... + def lt_u8(self, rhs: int) -> PySeries: ... + def lt_u16(self, rhs: int) -> PySeries: ... + def lt_u32(self, rhs: int) -> PySeries: ... + def lt_u64(self, rhs: int) -> PySeries: ... + def lt_i8(self, rhs: int) -> PySeries: ... + def lt_i16(self, rhs: int) -> PySeries: ... + def lt_i32(self, rhs: int) -> PySeries: ... + def lt_i64(self, rhs: int) -> PySeries: ... + def lt_i128(self, rhs: int) -> PySeries: ... + def lt_f16(self, rhs: float) -> PySeries: ... + def lt_f32(self, rhs: float) -> PySeries: ... + def lt_f64(self, rhs: float) -> PySeries: ... + def lt_str(self, rhs: str) -> PySeries: ... + def lt_decimal(self, rhs: Any) -> PySeries: ... + def lt_eq_u8(self, rhs: int) -> PySeries: ... + def lt_eq_u16(self, rhs: int) -> PySeries: ... + def lt_eq_u32(self, rhs: int) -> PySeries: ... + def lt_eq_u64(self, rhs: int) -> PySeries: ... + def lt_eq_u128(self, rhs: int) -> PySeries: ... + def lt_eq_i8(self, rhs: int) -> PySeries: ... + def lt_eq_i16(self, rhs: int) -> PySeries: ... + def lt_eq_i32(self, rhs: int) -> PySeries: ... + def lt_eq_i64(self, rhs: int) -> PySeries: ... + def lt_eq_i128(self, rhs: int) -> PySeries: ... + def lt_eq_f16(self, rhs: float) -> PySeries: ... + def lt_eq_f32(self, rhs: float) -> PySeries: ... + def lt_eq_f64(self, rhs: float) -> PySeries: ... + def lt_eq_str(self, rhs: str) -> PySeries: ... + def lt_eq_decimal(self, rhs: Any) -> PySeries: ... + + # construction + @staticmethod + def new_i8(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_i16(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_i32(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_i64(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_u8(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_u16(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_u32(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_u64(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_bool(name: str, array: NDArray1D, _strict: bool) -> PySeries: ... + @staticmethod + def new_f16(name: str, array: NDArray1D, nan_is_null: bool) -> PySeries: ... + @staticmethod + def new_f32(name: str, array: NDArray1D, nan_is_null: bool) -> PySeries: ... + @staticmethod + def new_f64(name: str, array: NDArray1D, nan_is_null: bool) -> PySeries: ... + @staticmethod + def new_opt_bool(name: str, values: Any, _strict: bool) -> PySeries: ... + @staticmethod + def new_opt_u8(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_u16(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_u32(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_u64(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_u128(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_i8(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_i16(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_i32(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_i64(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_i128(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_f16(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_f32(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_opt_f64(name: str, obj: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_from_any_values(name: str, values: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_from_any_values_and_dtype( + name: str, values: Any, dtype: DataType, strict: bool + ) -> PySeries: ... + @staticmethod + def new_str(name: str, values: Any, _strict: bool) -> PySeries: ... + @staticmethod + def new_binary(name: str, values: Any, _strict: bool) -> PySeries: ... + @staticmethod + def new_decimal(name: str, values: Any, strict: bool) -> PySeries: ... + @staticmethod + def new_series_list( + name: str, values: Sequence[PySeries | None], _strict: bool + ) -> PySeries: ... + @staticmethod + def new_array( + name: str, values: Any, strict: bool, dtype: DataType + ) -> PySeries: ... + @staticmethod + def new_object(name: str, values: Sequence[Any], _strict: bool) -> PySeries: ... + @staticmethod + def new_null(name: str, values: Any, _strict: bool) -> PySeries: ... + @staticmethod + def new_ext(name: str, values: Any, strict: bool, dtype: DataType) -> PySeries: ... + @staticmethod + def from_arrow(name: str, array: Any) -> PySeries: ... + + # export + def to_list(self) -> pylist[Any]: ... + def to_arrow(self, compat_level: Any) -> Any: ... + def __arrow_c_stream__(self, requested_schema: Any | None) -> Any: ... + def _export(self, location: int) -> None: ... + + # import + @classmethod + def from_arrow_c_array(cls, ob: Any) -> PySeries: ... + @classmethod + def from_arrow_c_stream(cls, ob: Any) -> PySeries: ... + @classmethod + def _import(cls, location: int) -> PySeries: ... + + # numpy ufunc + def apply_ufunc_f32(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_f64(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_u8(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_u16(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_u32(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_u64(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_i8(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_i16(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_i32(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + def apply_ufunc_i64(self, lambda_func: Any, allocate_out: bool) -> PySeries: ... + + # scatter + def scatter(self, idx: PySeries, values: PySeries) -> None: ... + + # interop + def to_numpy(self, writable: bool, allow_copy: bool) -> Any: ... + def to_numpy_view(self) -> Any | None: ... + def to_numpy_masked( + self, writable: bool, allow_copy: bool + ) -> tuple[Any, Any | None]: ... + @staticmethod + def _import_decimal_from_iceberg_binary_repr( + *, bytes_list: Sequence[bytes | None], precision: int, scale: int + ) -> PySeries: ... + +class PyDataFrame: + # general + @overload + def __init__(self, columns: Sequence[PySeries]) -> None: ... + @overload + def __init__(self, data: Any, columns: Any, orient: Any) -> None: ... + @overload + def __init__(self, schema: dict[str, Any]) -> None: ... + @staticmethod + def empty_with_height(height: int) -> PyDataFrame: ... + def estimated_size(self) -> int: ... + def dtype_strings(self) -> pylist[str]: ... + def add(self, s: PySeries) -> PyDataFrame: ... + def sub(self, s: PySeries) -> PyDataFrame: ... + def mul(self, s: PySeries) -> PyDataFrame: ... + def div(self, s: PySeries) -> PyDataFrame: ... + def rem(self, s: PySeries) -> PyDataFrame: ... + def add_df(self, s: PyDataFrame) -> PyDataFrame: ... + def sub_df(self, s: PyDataFrame) -> PyDataFrame: ... + def mul_df(self, s: PyDataFrame) -> PyDataFrame: ... + def div_df(self, s: PyDataFrame) -> PyDataFrame: ... + def rem_df(self, s: PyDataFrame) -> PyDataFrame: ... + def sample_n( + self, + n: PySeries, + with_replacement: bool, + shuffle: bool | None, + seed: int | None, + ) -> PyDataFrame: ... + def sample_frac( + self, + frac: PySeries, + with_replacement: bool, + shuffle: bool | None, + seed: int | None, + ) -> PyDataFrame: ... + def rechunk(self) -> PyDataFrame: ... + def as_str(self) -> str: ... + def get_columns(self) -> pylist[PySeries]: ... + def columns(self) -> pylist[str]: ... + def set_column_names(self, names: Sequence[str]) -> None: ... + def dtypes(self) -> pylist[Any]: ... + def n_chunks(self) -> int: ... + def shape(self) -> tuple[int, int]: ... + def height(self) -> int: ... + def width(self) -> int: ... + def is_empty(self) -> bool: ... + def hstack(self, columns: Sequence[PySeries]) -> PyDataFrame: ... + def hstack_mut(self, columns: Sequence[PySeries]) -> None: ... + def vstack(self, other: PyDataFrame) -> PyDataFrame: ... + def vstack_mut(self, other: PyDataFrame) -> None: ... + def extend(self, other: PyDataFrame) -> None: ... + def drop_in_place(self, name: str) -> PySeries: ... + def to_series(self, index: int) -> PySeries: ... + def get_column_index(self, name: str) -> int: ... + def get_column(self, name: str) -> PySeries: ... + def select(self, columns: Sequence[str]) -> PyDataFrame: ... + def gather(self, indices: Sequence[int]) -> PyDataFrame: ... + def gather_with_series(self, indices: PySeries) -> PyDataFrame: ... + def replace(self, column: str, new_col: PySeries) -> None: ... + def replace_column(self, index: int, new_column: PySeries) -> None: ... + def insert_column(self, index: int, column: PySeries) -> None: ... + def slice(self, offset: int, length: int | None) -> PyDataFrame: ... + def head(self, n: int) -> PyDataFrame: ... + def tail(self, n: int) -> PyDataFrame: ... + def is_unique(self) -> PySeries: ... + def is_duplicated(self) -> PySeries: ... + def is_sorted( + self, + by: Sequence[str], + descending: Sequence[bool], + nulls_last: Sequence[bool], + ) -> bool: ... + def equals(self, other: PyDataFrame, null_equal: bool) -> bool: ... + def with_row_index(self, name: str, offset: int | None) -> PyDataFrame: ... + def _to_metadata(self) -> PyDataFrame: ... + def group_by_map_groups( + self, by: Sequence[str], lambda_func: Any, maintain_order: bool + ) -> PyDataFrame: ... + def clone(self) -> PyDataFrame: ... + def unpivot( + self, + on: Sequence[str] | None, + index: Sequence[str], + value_name: str | None, + variable_name: str | None, + ) -> PyDataFrame: ... + def partition_by( + self, by: Sequence[str], maintain_order: bool, include_key: bool + ) -> pylist[PyDataFrame]: ... + def lazy(self) -> PyLazyFrame: ... + def n_unique(self, subset: Sequence[PyExpr] | None) -> int: ... + def to_dummies( + self, + columns: Sequence[str] | None, + separator: str | None, + drop_first: bool, + drop_nulls: bool, + ) -> PyDataFrame: ... + def null_count(self) -> PyDataFrame: ... + def map_rows( + self, + lambda_func: Any, + output_type: Any | None, + inference_size: int, + ) -> tuple[Any, bool]: ... + def shrink_to_fit(self) -> None: ... + def hash_rows(self, seed: int) -> PySeries: ... + def transpose( + self, keep_names_as: str | None, column_names: None | str | Sequence[str] + ) -> PyDataFrame: ... + def upsample( + self, + by: Sequence[str], + index_column: str, + every: str, + stable: bool, + ) -> PyDataFrame: ... + def to_struct(self, name: str, invalid_indices: Sequence[int]) -> PySeries: ... + def clear(self) -> PyDataFrame: ... + def _export_columns(self, location: int) -> None: ... + @classmethod + def _import_columns(cls, location: int, width: int) -> PyDataFrame: ... + def _row_encode(self, opts: Sequence[tuple[bool, bool, bool]]) -> PySeries: ... + + # construction + @staticmethod + def from_rows( + data: Sequence[PySeries], + schema: Any | None, + infer_schema_length: int | None, + ) -> PyDataFrame: ... + @staticmethod + def from_dicts( + data: Any, + schema: Any | None, + schema_overrides: Any | None, + strict: bool, + infer_schema_length: int | None, + ) -> PyDataFrame: ... + @staticmethod + def from_arrow_record_batches( + rb: Sequence[Any], + schema: Any, + ) -> PyDataFrame: ... + + # export + def row_tuple(self, idx: int) -> tuple[Any, ...]: ... + def row_tuples(self) -> pylist[tuple[Any, ...]]: ... + def to_arrow(self, compat_level: Any) -> pylist[Any]: ... + def to_pandas(self) -> pylist[Any]: ... + def __arrow_c_stream__(self, requested_schema: Any | None) -> Any: ... + + # io + @staticmethod + def read_csv( + py_f: Any, + infer_schema_length: int | None, + chunk_size: int, + has_header: bool, + ignore_errors: bool, + n_rows: int | None, + skip_rows: int, + skip_lines: int, + projection: Sequence[int] | None, + separator: str, + rechunk: bool, + columns: Sequence[str] | None, + encoding: Any, + n_threads: int | None, + path: str | None, + overwrite_dtype: Sequence[tuple[str, DataType]] | None, + overwrite_dtype_slice: Sequence[DataType] | None, + low_memory: bool, + comment_prefix: str | None, + quote_char: str | None, + null_values: Any | None, + empty_string_is_null: bool, + try_parse_dates: bool, + skip_rows_after_header: int, + row_index: tuple[str, int] | None, + eol_char: str, + raise_if_empty: bool, + truncate_ragged_lines: bool, + decimal_comma: bool, + schema: Any | None, + ) -> PyDataFrame: ... + @staticmethod + def read_json( + py_f: Any, + infer_schema_length: int | None, + schema: Any | None, + schema_overrides: Any | None, + ) -> PyDataFrame: ... + @staticmethod + def read_ipc( + py_f: Any, + columns: Sequence[str] | None, + projection: Sequence[int] | None, + n_rows: int | None, + row_index: tuple[str, int] | None, + memory_map: bool, + ) -> PyDataFrame: ... + @staticmethod + def read_ipc_stream( + py_f: Any, + columns: Sequence[str] | None, + projection: Sequence[int] | None, + n_rows: int | None, + row_index: tuple[str, int] | None, + rechunk: bool, + ) -> PyDataFrame: ... + @staticmethod + def read_avro( + py_f: Any, + columns: Sequence[str] | None, + projection: Sequence[int] | None, + n_rows: int | None, + ) -> PyDataFrame: ... + def write_json(self, py_f: Any) -> None: ... + def write_ipc_stream( + self, py_f: Any, compression: Any, compat_level: Any + ) -> None: ... + def write_avro(self, py_f: Any, compression: Any, name: str) -> None: ... + + # serde + def serialize_binary(self, py_f: Any) -> None: ... + @staticmethod + def deserialize_binary(py_f: Any) -> PyDataFrame: ... + def serialize_json(self, py_f: Any) -> None: ... + @staticmethod + def deserialize_json(py_f: Any) -> PyDataFrame: ... + + # interop + def to_numpy( + self, + order: IndexOrder, + writable: bool, + allow_copy: bool, + ) -> Any: ... + def to_numpy_masked( + self, + order: IndexOrder, + writable: bool, + allow_copy: bool, + ) -> tuple[Any, Any | None]: ... + +class PyLazyFrame: + @staticmethod + def new_from_ndjson( + source: Any | None, + sources: Any, + infer_schema_length: int | None, + schema: Any | None, + schema_overrides: Any | None, + batch_size: int | None, + n_rows: int | None, + low_memory: bool, + rechunk: bool, + row_index: tuple[str, int] | None, + ignore_errors: bool, + include_file_paths: str | None, + cloud_options: dict[str, Any] | None, + credential_provider: Any | None, + ) -> PyLazyFrame: ... + @staticmethod + def new_from_csv( + source: Any | None, + sources: Any, + separator: str, + has_header: bool, + ignore_errors: bool, + skip_rows: int, + skip_lines: int, + n_rows: int | None, + overwrite_dtype: Sequence[tuple[str, Any]] | None, + overwrite_dtype_slice: Sequence[DataType] | None, + low_memory: bool, + comment_prefix: str | None, + quote_char: str | None, + null_values: Any | None, + empty_string_is_null: bool, + infer_schema_length: int | None, + infer_schema_files: int, + with_schema_modify: Any | None, + rechunk: bool, + skip_rows_after_header: int, + encoding: Any, + row_index: tuple[str, int] | None, + try_parse_dates: bool, + eol_char: str, + raise_if_empty: bool, + truncate_ragged_lines: bool, + decimal_comma: bool, + glob: bool, + schema: Any | None, + new_columns: Sequence[str] | None, + cloud_options: dict[str, Any] | None, + credential_provider: Any | None, + include_file_paths: str | None, + extra_columns: str | None, + missing_columns: str | None, + ) -> PyLazyFrame: ... + @staticmethod + def new_from_parquet( + sources: Any, + schema: Any | None, + scan_options: ScanOptions, + parallel: Any, + low_memory: bool, + use_statistics: bool, + decryption_properties: PyFileDecryptionProperties | None, + ) -> PyLazyFrame: ... + @staticmethod + def new_from_ipc( + sources: Any, + record_batch_statistics: bool | None, + scan_options: ScanOptions, + ) -> PyLazyFrame: ... + @staticmethod + def new_from_scan_lines( + sources: Any, + *, + name: str, + scan_options: ScanOptions, + ) -> PyLazyFrame: ... + @staticmethod + def new_from_expand_paths( + sources: Any, + *, + name: str, + scan_options: ScanOptions, + ) -> PyLazyFrame: ... + @staticmethod + def new_from_dataset_object( + dataset_object: Any, resolve_heavy_sources: int | None = None + ) -> PyLazyFrame: ... + @staticmethod + def from_lazyframe_resolver(resolver: LazyFrameResolver) -> PyLazyFrame: ... + @staticmethod + def new_from_external_reader_builder( + sources: Any, + reader_builder: Any, + schema: Any, + scan_options: ScanOptions, + ) -> PyLazyFrame: ... + @staticmethod + def scan_from_python_function_arrow_schema( + schema: Any, + scan_fn: Any, + pyarrow: bool, + validate_schema: bool, + is_pure: bool, + *, + explain_name: str | None = None, + explain_detail: str | None = None, + ) -> PyLazyFrame: ... + @staticmethod + def scan_from_python_function_pl_schema( + schema: Sequence[tuple[str, Any]], + scan_fn: Any, + pyarrow: bool, + validate_schema: bool, + is_pure: bool, + *, + explain_name: str | None = None, + explain_detail: str | None = None, + ) -> PyLazyFrame: ... + @staticmethod + def scan_from_python_function_schema_function( + schema_fn: Any, + scan_fn: Any, + validate_schema: bool, + is_pure: bool, + *, + explain_name: str | None = None, + explain_detail: str | None = None, + ) -> PyLazyFrame: ... + def pipe_with_schema( + self, callback: Callable[[tuple[PyLazyFrame, Schema]], PyLazyFrame] + ) -> PyLazyFrame: ... + def describe_plan(self) -> str: ... + def describe_optimized_plan(self) -> str: ... + def describe_plan_tree(self) -> str: ... + def describe_optimized_plan_tree(self) -> str: ... + def _retained_parquet_footers(self) -> pylist[pylist[tuple[int, int]]]: ... + def to_dot(self, optimized: bool) -> str: ... + def to_dot_streaming_phys(self, optimized: bool) -> str: ... + def sort( + self, + by_column: str, + descending: bool, + nulls_last: bool, + maintain_order: bool, + multithreaded: bool, + ) -> PyLazyFrame: ... + def sort_by_exprs( + self, + by: Sequence[PyExpr], + descending: Sequence[bool], + nulls_last: Sequence[bool], + maintain_order: bool, + multithreaded: bool, + ) -> PyLazyFrame: ... + def top_k( + self, k: int, by: Sequence[PyExpr], reverse: Sequence[bool] + ) -> PyLazyFrame: ... + def bottom_k( + self, k: int, by: Sequence[PyExpr], reverse: Sequence[bool] + ) -> PyLazyFrame: ... + def cache(self) -> PyLazyFrame: ... + def with_optimizations(self, optflags: PyOptFlags) -> PyLazyFrame: ... + def collect(self, engine: Any, lambda_post_opt: Any | None) -> PyDataFrame: ... + def collect_with_callback(self, engine: Any, lambda_func: Any) -> None: ... + def collect_batches( + self, engine: Any, maintain_order: bool, chunk_size: int | None, lazy: bool + ) -> PyCollectBatches: ... + def sink_parquet( + self, + target: SinkTarget, + sink_options: Any, + compression: str, + compression_level: int | None, + statistics: StatisticsOptions, + row_group_size: int | None, + data_page_size: int | None, + metadata: KeyValueMetadata | None, + arrow_schema: ArrowSchemaExportable | None = None, + encryption_properties: PyFileEncryptionProperties | None = None, + ) -> PyLazyFrame: ... + def sink_ipc( + self, + target: SinkTarget, + sink_options: Any, + compression: IpcCompression | None, + compat_level: CompatLevel, + record_batch_size: int | None, + record_batch_statistics: bool | None, + ) -> PyLazyFrame: ... + def sink_csv( + self, + target: SinkTarget, + sink_options: Any, + include_bom: bool, + compression: Literal["uncompressed", "gzip", "zstd"], + compression_level: int | None, + check_extension: bool, + include_header: bool, + separator: int, + line_terminator: str, + quote_char: int, + batch_size: int, + datetime_format: str | None, + date_format: str | None, + time_format: str | None, + float_scientific: bool | None, + float_precision: int | None, + decimal_comma: bool, + null_value: str | None, + quote_style: QuoteStyle | None, + ) -> PyLazyFrame: ... + def sink_ndjson( + self, + target: SinkTarget, + compression: Literal["uncompressed", "gzip", "zstd"], + compression_level: int | None, + check_extension: bool, + sink_options: Any, + ) -> PyLazyFrame: ... + def sink_batches( + self, + function: Callable[[PyDataFrame], bool], + maintain_order: bool, + chunk_size: int | None, + ) -> PyLazyFrame: ... + def sink_iceberg(self, sink_state_obj: IcebergSinkState) -> PyLazyFrame: ... + def filter(self, predicate: PyExpr) -> PyLazyFrame: ... + def remove(self, predicate: PyExpr) -> PyLazyFrame: ... + def select(self, exprs: Sequence[PyExpr]) -> PyLazyFrame: ... + def select_seq(self, exprs: Sequence[PyExpr]) -> PyLazyFrame: ... + def group_by(self, by: Sequence[PyExpr], maintain_order: bool) -> PyLazyGroupBy: ... + def rolling( + self, + index_column: PyExpr, + period: str, + offset: str, + closed: ClosedWindow, + by: Sequence[PyExpr], + ) -> PyLazyGroupBy: ... + def group_by_dynamic( + self, + index_column: PyExpr, + every: str, + period: str, + offset: str, + label: Label, + include_boundaries: bool, + closed: ClosedWindow, + group_by: Sequence[PyExpr], + start_by: StartBy, + ) -> PyLazyGroupBy: ... + def join_asof( + self, + other: PyLazyFrame, + left_on: PyExpr, + right_on: PyExpr, + left_by: Sequence[str] | None, + right_by: Sequence[str] | None, + allow_parallel: bool, + force_parallel: bool, + suffix: str, + strategy: AsofStrategy, + tolerance: Any | None, + tolerance_str: str | None, + coalesce: bool, + allow_eq: bool, + check_sortedness: bool, + ) -> PyLazyFrame: ... + def join( + self, + other: PyLazyFrame, + left_on: Sequence[PyExpr], + right_on: Sequence[PyExpr], + allow_parallel: bool, + force_parallel: bool, + nulls_equal: bool, + how: JoinType, + suffix: str, + validate: JoinValidation, + maintain_order: MaintainOrderJoin, + build_side: JoinBuildSide, + coalesce: bool | None, + ) -> PyLazyFrame: ... + def join_where( + self, + other: PyLazyFrame, + predicates: Sequence[PyExpr], + how: JoinType, + suffix: str, + ) -> PyLazyFrame: ... + def gather(self, other: PyLazyFrame, null_on_oob: bool) -> PyLazyFrame: ... + def with_columns(self, exprs: Sequence[PyExpr]) -> PyLazyFrame: ... + def with_columns_seq(self, exprs: Sequence[PyExpr]) -> PyLazyFrame: ... + def match_to_schema( + self, + schema: Schema, + missing_columns: Any, + missing_struct_fields: Any, + extra_columns: ExtraColumnsPolicy, + extra_struct_fields: Any, + integer_cast: Any, + float_cast: Any, + ) -> PyLazyFrame: ... + def rename( + self, existing: Sequence[str], new: Sequence[str], strict: bool + ) -> PyLazyFrame: ... + def reverse(self) -> PyLazyFrame: ... + def shift(self, n: PyExpr, fill_value: PyExpr | None) -> PyLazyFrame: ... + def fill_nan(self, fill_value: PyExpr) -> PyLazyFrame: ... + def min(self) -> PyLazyFrame: ... + def max(self) -> PyLazyFrame: ... + def sum(self) -> PyLazyFrame: ... + def mean(self) -> PyLazyFrame: ... + def std(self, ddof: int) -> PyLazyFrame: ... + def var(self, ddof: int) -> PyLazyFrame: ... + def median(self) -> PyLazyFrame: ... + def quantile( + self, quantile: PyExpr, interpolation: QuantileMethod + ) -> PyLazyFrame: ... + def explode( + self, subset: PySelector, *, empty_as_null: bool, keep_nulls: bool + ) -> PyLazyFrame: ... + def null_count(self) -> PyLazyFrame: ... + def unique( + self, + maintain_order: bool, + subset: pylist[PyExpr] | None, + keep: UniqueKeepStrategy, + ) -> PyLazyFrame: ... + def drop_nans(self, subset: PySelector | None) -> PyLazyFrame: ... + def drop_nulls(self, subset: PySelector | None) -> PyLazyFrame: ... + def slice(self, offset: int, len: int | None) -> PyLazyFrame: ... + def tail(self, n: int) -> PyLazyFrame: ... + def pivot( + self, + on: PySelector, + on_columns: PyDataFrame, + index: PySelector, + values: PySelector, + agg: PyExpr, + maintain_order: bool, + separator: str, + column_naming: Literal["auto", "combine"], + ) -> PyLazyFrame: ... + def unpivot( + self, + on: PySelector | None, + index: PySelector, + value_name: str | None, + variable_name: str | None, + ) -> PyLazyFrame: ... + def with_row_index(self, name: str, offset: int | None = None) -> PyLazyFrame: ... + def map_batches( + self, + function: Any, + predicate_pushdown: bool, + projection_pushdown: bool, + slice_pushdown: bool, + streamable: bool, + schema: Schema | None, + validate_output: bool, + ) -> PyLazyFrame: ... + def drop(self, columns: PySelector) -> PyLazyFrame: ... + def cast(self, dtypes: dict[str, DataType], strict: bool) -> PyLazyFrame: ... + def cast_all(self, dtype: PyDataTypeExpr, strict: bool) -> PyLazyFrame: ... + def clone(self) -> PyLazyFrame: ... + def collect_schema(self) -> dict[str, Any]: ... + def unnest(self, columns: PySelector, separator: str | None) -> PyLazyFrame: ... + def count(self) -> PyLazyFrame: ... + def merge_sorted( + self, other: PyLazyFrame, key: pylist[str], maintain_order: bool + ) -> PyLazyFrame: ... + def hint_sorted( + self, + columns: pylist[str], + descending: pylist[bool], + nulls_last: pylist[bool], + ) -> PyLazyFrame: ... + + # exitable + def collect_concurrently(self) -> PyInProcessQuery: ... + + # serde + def serialize_binary(self, py_f: Any) -> None: ... + def serialize_json(self, py_f: Any) -> None: ... + @staticmethod + def deserialize_binary(py_f: Any) -> PyLazyFrame: ... + @staticmethod + def deserialize_json(py_f: Any) -> PyLazyFrame: ... + + # visit + def visit(self) -> NodeTraverser: ... + +class PyInProcessQuery: + def cancel(self) -> None: ... + def fetch(self) -> PyDataFrame | None: ... + def fetch_blocking(self) -> PyDataFrame: ... + +class PyExpr: + def __init__(self, inner: Any) -> None: ... + def __richcmp__(self, other: PyExpr, op: Any) -> PyExpr: ... + def __add__(self, rhs: PyExpr) -> PyExpr: ... + def __sub__(self, rhs: PyExpr) -> PyExpr: ... + def __mul__(self, rhs: PyExpr) -> PyExpr: ... + def __truediv__(self, rhs: PyExpr) -> PyExpr: ... + def __mod__(self, rhs: PyExpr) -> PyExpr: ... + def __floordiv__(self, rhs: PyExpr) -> PyExpr: ... + def trunc_div(self, rhs: PyExpr) -> PyExpr: ... + def __neg__(self) -> PyExpr: ... + def to_str(self) -> str: ... + def eq(self, other: PyExpr) -> PyExpr: ... + def eq_missing(self, other: PyExpr) -> PyExpr: ... + def neq(self, other: PyExpr) -> PyExpr: ... + def neq_missing(self, other: PyExpr) -> PyExpr: ... + def gt(self, other: PyExpr) -> PyExpr: ... + def gt_eq(self, other: PyExpr) -> PyExpr: ... + def lt_eq(self, other: PyExpr) -> PyExpr: ... + def lt(self, other: PyExpr) -> PyExpr: ... + def alias(self, name: str) -> PyExpr: ... + def not_(self) -> PyExpr: ... + def is_null(self) -> PyExpr: ... + def is_not_null(self) -> PyExpr: ... + def is_infinite(self) -> PyExpr: ... + def is_finite(self) -> PyExpr: ... + def is_nan(self) -> PyExpr: ... + def is_not_nan(self) -> PyExpr: ... + def min(self) -> PyExpr: ... + def min_by(self, other: PyExpr) -> PyExpr: ... + def max(self) -> PyExpr: ... + def max_by(self, other: PyExpr) -> PyExpr: ... + def nan_max(self) -> PyExpr: ... + def nan_min(self) -> PyExpr: ... + def mean(self) -> PyExpr: ... + def median(self) -> PyExpr: ... + def sum(self) -> PyExpr: ... + def n_unique(self) -> PyExpr: ... + def arg_unique(self) -> PyExpr: ... + def unique(self) -> PyExpr: ... + def unique_stable(self) -> PyExpr: ... + def first(self, ignore_nulls: bool) -> PyExpr: ... + def last(self, ignore_nulls: bool) -> PyExpr: ... + def item(self, *, allow_empty: bool) -> PyExpr: ... + def implode(self, maintain_order: bool) -> PyExpr: ... + def quantile(self, quantile: PyExpr, interpolation: Any) -> PyExpr: ... + def cut( + self, + breaks: Sequence[float], + labels: Sequence[str] | None, + left_closed: bool, + include_breaks: bool, + ) -> PyExpr: ... + def qcut( + self, + probs: Sequence[float], + labels: Sequence[str] | None, + left_closed: bool, + allow_duplicates: bool, + include_breaks: bool, + ) -> PyExpr: ... + def qcut_uniform( + self, + n_bins: int, + labels: Sequence[str] | None, + left_closed: bool, + allow_duplicates: bool, + include_breaks: bool, + ) -> PyExpr: ... + def bin_intervals( + self, + intervals: PySeries, + labels: Sequence[str] | None, + include_intervals: bool, + right_closed: bool, + ) -> PyExpr: ... + def bin_intervals_uniform( + self, + n_bins: int, + labels: Sequence[str] | None, + include_intervals: bool, + right_closed: bool, + ) -> PyExpr: ... + def bin_quantiles( + self, + quantiles: Sequence[float], + labels: Sequence[str] | None, + include_intervals: bool, + right_closed: bool, + ) -> PyExpr: ... + def bin_quantiles_uniform( + self, + n_bins: int, + labels: Sequence[str] | None, + include_intervals: bool, + right_closed: bool, + ) -> PyExpr: ... + def bin_ranks( + self, + ranks: Sequence[float], + labels: Sequence[str] | None, + include_intervals: bool, + ) -> PyExpr: ... + def bin_ranks_uniform( + self, + n_bins: int, + labels: Sequence[str] | None, + include_intervals: bool, + ) -> PyExpr: ... + def rle(self) -> PyExpr: ... + def rle_id(self) -> PyExpr: ... + def count(self) -> PyExpr: ... + def len(self) -> PyExpr: ... + def value_counts( + self, sort: bool, parallel: bool, name: str, normalize: bool + ) -> PyExpr: ... + def unique_counts(self) -> PyExpr: ... + def null_count(self) -> PyExpr: ... + def cast( + self, dtype: PyDataTypeExpr, strict: bool, wrap_numerical: bool + ) -> PyExpr: ... + def sort_with(self, descending: bool, nulls_last: bool) -> PyExpr: ... + def arg_sort(self, descending: bool, nulls_last: bool) -> PyExpr: ... + def top_k(self, k: PyExpr) -> PyExpr: ... + def top_k_by( + self, by: Sequence[PyExpr], k: PyExpr, reverse: Sequence[bool] + ) -> PyExpr: ... + def bottom_k(self, k: PyExpr) -> PyExpr: ... + def bottom_k_by( + self, by: Sequence[PyExpr], k: PyExpr, reverse: Sequence[bool] + ) -> PyExpr: ... + def peak_min(self) -> PyExpr: ... + def peak_max(self) -> PyExpr: ... + def arg_max(self) -> PyExpr: ... + def arg_min(self) -> PyExpr: ... + def index_of(self, element: PyExpr) -> PyExpr: ... + def search_sorted(self, element: PyExpr, side: Any, descending: bool) -> PyExpr: ... + def gather(self, idx: PyExpr, null_on_oob: bool) -> PyExpr: ... + def get( + self, + idx: PyExpr, + *, + null_on_oob: bool, + ) -> PyExpr: ... + def sort_by( + self, + by: Sequence[PyExpr], + descending: Sequence[bool], + nulls_last: Sequence[bool], + multithreaded: bool, + maintain_order: bool, + ) -> PyExpr: ... + def shift(self, n: PyExpr, fill_value: PyExpr | None) -> PyExpr: ... + def fill_null(self, expr: PyExpr) -> PyExpr: ... + def fill_null_with_strategy(self, strategy: str, limit: Any) -> PyExpr: ... + def fill_nan(self, expr: PyExpr) -> PyExpr: ... + def drop_nulls(self) -> PyExpr: ... + def drop_nans(self) -> PyExpr: ... + def filter(self, predicate: PyExpr) -> PyExpr: ... + def reverse(self) -> PyExpr: ... + def std(self, ddof: int) -> PyExpr: ... + def var(self, ddof: int) -> PyExpr: ... + def is_unique(self) -> PyExpr: ... + def is_between(self, lower: PyExpr, upper: PyExpr, closed: Any) -> PyExpr: ... + def is_close( + self, other: PyExpr, abs_tol: float, rel_tol: float, nans_equal: bool + ) -> PyExpr: ... + def is_sorted(self, descending: bool | None, nulls_last: bool | None) -> PyExpr: ... + def approx_n_unique(self) -> PyExpr: ... + def approx_quantile( + self, + quantile: float | Sequence[float] | PyExpr, + method: ApproxQuantileMethod, + error: float, + use_formal_bound: bool, + ) -> PyExpr: ... + def is_first_distinct(self) -> PyExpr: ... + def is_last_distinct(self) -> PyExpr: ... + def explode(self, *, empty_as_null: bool, keep_nulls: bool) -> PyExpr: ... + def gather_every(self, n: int, offset: int) -> PyExpr: ... + def slice(self, offset: PyExpr, length: PyExpr) -> PyExpr: ... + def append(self, other: PyExpr, upcast: bool) -> PyExpr: ... + def rechunk(self) -> PyExpr: ... + def round(self, decimals: int, mode: Any) -> PyExpr: ... + def round_sig_figs(self, digits: int) -> PyExpr: ... + def truncate(self, decimals: int) -> PyExpr: ... + def floor(self) -> PyExpr: ... + def ceil(self) -> PyExpr: ... + def clip(self, min: PyExpr | None, max: PyExpr | None) -> PyExpr: ... + def abs(self) -> PyExpr: ... + def sin(self) -> PyExpr: ... + def cos(self) -> PyExpr: ... + def tan(self) -> PyExpr: ... + def cot(self) -> PyExpr: ... + def arcsin(self) -> PyExpr: ... + def arccos(self) -> PyExpr: ... + def arctan(self) -> PyExpr: ... + def arctan2(self, y: PyExpr) -> PyExpr: ... + def sinh(self) -> PyExpr: ... + def cosh(self) -> PyExpr: ... + def tanh(self) -> PyExpr: ... + def arcsinh(self) -> PyExpr: ... + def arccosh(self) -> PyExpr: ... + def arctanh(self) -> PyExpr: ... + def degrees(self) -> PyExpr: ... + def radians(self) -> PyExpr: ... + def sign(self) -> PyExpr: ... + def is_duplicated(self) -> PyExpr: ... + def over( + self, + partition_by: Sequence[PyExpr] | None, + order_by: Sequence[PyExpr] | None, + order_by_descending: bool, + order_by_nulls_last: bool, + mapping_strategy: Any, + ) -> PyExpr: ... + def rolling( + self, index_column: PyExpr, period: str, offset: str, closed: Any + ) -> PyExpr: ... + def and_(self, expr: PyExpr) -> PyExpr: ... + def or_(self, expr: PyExpr) -> PyExpr: ... + def xor_(self, expr: PyExpr) -> PyExpr: ... + def is_in(self, expr: PyExpr, nulls_equal: bool) -> PyExpr: ... + def repeat_by(self, by: PyExpr) -> PyExpr: ... + def pow(self, exponent: PyExpr) -> PyExpr: ... + def sqrt(self) -> PyExpr: ... + def cbrt(self) -> PyExpr: ... + def cum_sum(self, reverse: bool) -> PyExpr: ... + def cum_max(self, reverse: bool) -> PyExpr: ... + def cum_min(self, reverse: bool) -> PyExpr: ... + def cum_prod(self, reverse: bool) -> PyExpr: ... + def cum_count(self, reverse: bool) -> PyExpr: ... + def cumulative_eval(self, expr: PyExpr, min_samples: int) -> PyExpr: ... + def product(self) -> PyExpr: ... + def shrink_dtype(self) -> PyExpr: ... + def dot(self, other: PyExpr) -> PyExpr: ... + def reinterpret(self, signed: bool | None, dtype: DataType | None) -> PyExpr: ... + def mode(self, *, maintain_order: bool) -> PyExpr: ... + def interpolate(self, method: Any) -> PyExpr: ... + def interpolate_by(self, by: PyExpr) -> PyExpr: ... + def lower_bound(self) -> PyExpr: ... + def upper_bound(self) -> PyExpr: ... + def rank(self, method: Any, descending: bool, seed: int | None) -> PyExpr: ... + def diff(self, n: PyExpr, null_behavior: Any) -> PyExpr: ... + def pct_change(self, n: PyExpr) -> PyExpr: ... + def skew(self, bias: bool) -> PyExpr: ... + def kurtosis(self, fisher: bool, bias: bool) -> PyExpr: ... + def reshape(self, dims: Sequence[int]) -> PyExpr: ... + def to_physical(self) -> PyExpr: ... + def shuffle(self, seed: int | None) -> PyExpr: ... + def sample_n( + self, n: PyExpr, with_replacement: bool, shuffle: bool | None, seed: int | None + ) -> PyExpr: ... + def sample_frac( + self, + frac: PyExpr, + with_replacement: bool, + shuffle: bool | None, + seed: int | None, + ) -> PyExpr: ... + def ewm_mean( + self, alpha: float, adjust: bool, min_periods: int, ignore_nulls: bool + ) -> PyExpr: ... + def ewm_sum(self, alpha: float, min_periods: int, ignore_nulls: bool) -> PyExpr: ... + def ewm_mean_by(self, times: PyExpr, half_life: str) -> PyExpr: ... + def ewm_sum_by(self, times: PyExpr, half_life: str) -> PyExpr: ... + def ewm_std( + self, + alpha: float, + adjust: bool, + bias: bool, + min_periods: int, + ignore_nulls: bool, + ) -> PyExpr: ... + def ewm_var( + self, + alpha: float, + adjust: bool, + bias: bool, + min_periods: int, + ignore_nulls: bool, + ) -> PyExpr: ... + def extend_constant(self, value: PyExpr, n: PyExpr) -> PyExpr: ... + def any(self, ignore_nulls: bool) -> PyExpr: ... + def all(self, ignore_nulls: bool) -> PyExpr: ... + def is_empty(self, ignore_nulls: bool) -> PyExpr: ... + def has_nulls(self) -> PyExpr: ... + def log(self, base: PyExpr) -> PyExpr: ... + def log1p(self) -> PyExpr: ... + def exp(self) -> PyExpr: ... + def erf(self) -> PyExpr: ... + def erfc(self) -> PyExpr: ... + def entropy(self, base: float, normalize: bool) -> PyExpr: ... + def hash(self, seed: int) -> PyExpr: ... + def mm3_hash(self) -> PyExpr: ... + def set_sorted_flag(self, descending: bool, nulls_last: bool | None) -> PyExpr: ... + def replace(self, old: PyExpr, new: PyExpr) -> PyExpr: ... + def replace_strict( + self, + old: PyExpr, + new: PyExpr, + default: PyExpr | None, + return_dtype: PyDataTypeExpr | None, + ) -> PyExpr: ... + def hist( + self, + bins: PyExpr | None, + bin_count: int | None, + include_category: bool, + include_breakpoint: bool, + ) -> PyExpr: ... + def skip_batch_predicate(self, schema: Any) -> PyExpr | None: ... + def to_pyarrow_predicate(self, schema: Any) -> str | None: ... + @staticmethod + def row_encode_unordered(exprs: Sequence[PyExpr]) -> PyExpr: ... + @staticmethod + def row_encode_ordered( + exprs: Sequence[PyExpr], + descending: Sequence[bool] | None, + nulls_last: Sequence[bool] | None, + ) -> PyExpr: ... + def row_decode_unordered( + self, names: Sequence[str], datatypes: Sequence[PyDataTypeExpr] + ) -> PyExpr: ... + def row_decode_ordered( + self, + names: Sequence[str], + datatypes: Sequence[PyDataTypeExpr], + descending: Sequence[bool] | None, + nulls_last: Sequence[bool] | None, + ) -> PyExpr: ... + def into_selector(self) -> Any: ... + @staticmethod + def new_selector(selector: Any) -> PyExpr: ... + + # array + def arr_len(self) -> PyExpr: ... + def arr_max(self) -> PyExpr: ... + def arr_min(self) -> PyExpr: ... + def arr_sum(self) -> PyExpr: ... + def arr_dot(self, other: PyExpr) -> PyExpr: ... + def arr_std(self, ddof: int) -> PyExpr: ... + def arr_var(self, ddof: int) -> PyExpr: ... + def arr_mean(self) -> PyExpr: ... + def arr_median(self) -> PyExpr: ... + def arr_to_list(self) -> PyExpr: ... + def arr_sort(self, descending: bool, nulls_last: bool) -> PyExpr: ... + def arr_arg_min(self) -> PyExpr: ... + def arr_arg_max(self) -> PyExpr: ... + def arr_get(self, index: PyExpr, null_on_oob: bool) -> PyExpr: ... + def arr_join(self, separator: PyExpr, ignore_nulls: bool) -> PyExpr: ... + def arr_contains(self, other: PyExpr, nulls_equal: bool) -> PyExpr: ... + def arr_count_matches(self, expr: PyExpr) -> PyExpr: ... + def arr_to_struct(self, name_gen: Any | None = None) -> PyExpr: ... + def arr_slice( + self, offset: PyExpr, length: PyExpr | None = None, as_array: bool = False + ) -> PyExpr: ... + def arr_tail(self, n: PyExpr, as_array: bool) -> PyExpr: ... + def arr_shift(self, n: PyExpr) -> PyExpr: ... + def arr_explode(self, *, empty_as_null: bool, keep_nulls: bool) -> PyExpr: ... + def arr_eval(self, expr: PyExpr, *, as_list: bool) -> PyExpr: ... + def arr_agg(self, expr: PyExpr) -> PyExpr: ... + + # binary + def bin_contains(self, lit: PyExpr) -> PyExpr: ... + def bin_ends_with(self, sub: PyExpr) -> PyExpr: ... + def bin_starts_with(self, sub: PyExpr) -> PyExpr: ... + def bin_hex_decode(self, strict: bool) -> PyExpr: ... + def bin_base64_decode(self, strict: bool) -> PyExpr: ... + def bin_hex_encode(self) -> PyExpr: ... + def bin_base64_encode(self) -> PyExpr: ... + def bin_reinterpret(self, dtype: PyDataTypeExpr, kind: str) -> PyExpr: ... + def bin_size_bytes(self) -> PyExpr: ... + def bin_slice(self, offset: PyExpr, length: PyExpr) -> PyExpr: ... + def bin_head(self, n: PyExpr) -> PyExpr: ... + def bin_tail(self, n: PyExpr) -> PyExpr: ... + def bin_get(self, index: PyExpr, null_on_oob: bool) -> PyExpr: ... + + # bitwise + def bitwise_count_ones(self) -> PyExpr: ... + def bitwise_count_zeros(self) -> PyExpr: ... + def bitwise_leading_ones(self) -> PyExpr: ... + def bitwise_leading_zeros(self) -> PyExpr: ... + def bitwise_trailing_ones(self) -> PyExpr: ... + def bitwise_trailing_zeros(self) -> PyExpr: ... + def bitwise_and(self) -> PyExpr: ... + def bitwise_or(self) -> PyExpr: ... + def bitwise_xor(self) -> PyExpr: ... + + # categorical + def cat_len_bytes(self) -> PyExpr: ... + def cat_len_chars(self) -> PyExpr: ... + def cat_starts_with(self, prefix: str) -> PyExpr: ... + def cat_ends_with(self, suffix: str) -> PyExpr: ... + def cat_slice(self, offset: int, length: int | None = None) -> PyExpr: ... + def cat_to(self, dtype: PyDataTypeExpr, strict: bool) -> PyExpr: ... + def cat_physical(self) -> PyExpr: ... + + # datetime + def dt_add_business_days( + self, n: PyExpr, week_mask: Sequence[bool], holidays: PyExpr, roll: Roll + ) -> PyExpr: ... + def dt_to_string(self, format: str) -> PyExpr: ... + def dt_offset_by(self, by: PyExpr) -> PyExpr: ... + def dt_convert_time_zone(self, time_zone: str) -> PyExpr: ... + def dt_cast_time_unit(self, time_unit: TimeUnit) -> PyExpr: ... + def dt_replace_time_zone( + self, + time_zone: str | None, + ambiguous: PyExpr, + non_existent: NonExistent, + ) -> PyExpr: ... + def dt_truncate(self, every: PyExpr) -> PyExpr: ... + def dt_month_start(self) -> PyExpr: ... + def dt_month_end(self) -> PyExpr: ... + def dt_base_utc_offset(self) -> PyExpr: ... + def dt_dst_offset(self) -> PyExpr: ... + def dt_round(self, every: PyExpr) -> PyExpr: ... + def dt_replace( + self, + year: PyExpr, + month: PyExpr, + day: PyExpr, + hour: PyExpr, + minute: PyExpr, + second: PyExpr, + microsecond: PyExpr, + ambiguous: PyExpr, + ) -> PyExpr: ... + def dt_combine(self, time: PyExpr, time_unit: TimeUnit) -> PyExpr: ... + def dt_millennium(self) -> PyExpr: ... + def dt_century(self) -> PyExpr: ... + def dt_year(self) -> PyExpr: ... + def dt_is_business_day( + self, week_mask: Sequence[bool], holidays: PyExpr + ) -> PyExpr: ... + def dt_is_leap_year(self) -> PyExpr: ... + def dt_iso_year(self) -> PyExpr: ... + def dt_quarter(self) -> PyExpr: ... + def dt_month(self) -> PyExpr: ... + def dt_days_in_month(self) -> PyExpr: ... + def dt_week(self) -> PyExpr: ... + def dt_weekday(self) -> PyExpr: ... + def dt_day(self) -> PyExpr: ... + def dt_ordinal_day(self) -> PyExpr: ... + def dt_time(self) -> PyExpr: ... + def dt_date(self) -> PyExpr: ... + def dt_hour(self) -> PyExpr: ... + def dt_minute(self) -> PyExpr: ... + def dt_second(self) -> PyExpr: ... + def dt_millisecond(self) -> PyExpr: ... + def dt_microsecond(self) -> PyExpr: ... + def dt_nanosecond(self) -> PyExpr: ... + def dt_timestamp(self, time_unit: TimeUnit) -> PyExpr: ... + def dt_total_days(self, fractional: bool) -> PyExpr: ... + def dt_total_hours(self, fractional: bool) -> PyExpr: ... + def dt_total_minutes(self, fractional: bool) -> PyExpr: ... + def dt_total_seconds(self, fractional: bool) -> PyExpr: ... + def dt_total_milliseconds(self, fractional: bool) -> PyExpr: ... + def dt_total_microseconds(self, fractional: bool) -> PyExpr: ... + def dt_total_nanoseconds(self, fractional: bool) -> PyExpr: ... + + # list + def list_arg_max(self) -> PyExpr: ... + def list_arg_min(self) -> PyExpr: ... + def list_contains(self, other: PyExpr, nulls_equal: bool) -> PyExpr: ... + def list_count_matches(self, expr: PyExpr) -> PyExpr: ... + def list_diff(self, n: int, null_behavior: NullBehavior) -> PyExpr: ... + def list_eval(self, expr: PyExpr, _parallel: bool) -> PyExpr: ... + def list_agg(self, expr: PyExpr) -> PyExpr: ... + def list_filter(self, predicate: PyExpr) -> PyExpr: ... + def list_get(self, index: PyExpr, null_on_oob: bool) -> PyExpr: ... + def list_join(self, separator: PyExpr, ignore_nulls: bool) -> PyExpr: ... + def list_len(self) -> PyExpr: ... + def list_max(self) -> PyExpr: ... + def list_mean(self) -> PyExpr: ... + def list_median(self) -> PyExpr: ... + def list_std(self, ddof: int) -> PyExpr: ... + def list_var(self, ddof: int) -> PyExpr: ... + def list_min(self) -> PyExpr: ... + def list_shift(self, periods: PyExpr) -> PyExpr: ... + def list_slice(self, offset: PyExpr, length: PyExpr | None = None) -> PyExpr: ... + def list_tail(self, n: PyExpr) -> PyExpr: ... + def list_sort(self, descending: bool, nulls_last: bool) -> PyExpr: ... + def list_sum(self) -> PyExpr: ... + def list_drop_nulls(self) -> PyExpr: ... + def list_sample_n( + self, + n: PyExpr, + with_replacement: bool, + shuffle: bool | None, + seed: int | None = None, + ) -> PyExpr: ... + def list_sample_fraction( + self, + fraction: PyExpr, + with_replacement: bool, + shuffle: bool | None, + seed: int | None = None, + ) -> PyExpr: ... + def list_gather(self, index: PyExpr, null_on_oob: bool) -> PyExpr: ... + def list_gather_every(self, n: PyExpr, offset: PyExpr) -> PyExpr: ... + def list_to_array(self, width: int) -> PyExpr: ... + def list_to_map(self) -> PyExpr: ... + def list_to_struct(self, names: Sequence[str]) -> PyExpr: ... + def list_to_struct_fixed_width(self, names: Sequence[str]) -> PyExpr: ... + def list_set_operation(self, other: PyExpr, operation: SetOperation) -> PyExpr: ... + + # meta + def meta_eq(self, other: PyExpr) -> bool: ... + def meta_pop(self, schema: Schema | None = None) -> pylist[PyExpr]: ... + def meta_root_names(self) -> pylist[str]: ... + def meta_output_name(self) -> str: ... + def meta_undo_aliases(self) -> PyExpr: ... + def meta_has_multiple_outputs(self) -> bool: ... + def meta_is_column(self) -> bool: ... + def meta_is_regex_projection(self) -> bool: ... + def meta_is_column_selection(self, allow_aliasing: bool) -> bool: ... + def meta_is_literal(self, allow_aliasing: bool) -> bool: ... + def meta_is_scalar(self) -> bool: ... + def meta_is_row_separable(self) -> bool: ... + def meta_is_length_preserving(self) -> bool: ... + def meta_is_known_length(self) -> bool: ... + def compute_tree_format( + self, display_as_dot: bool, schema: Schema | None + ) -> str: ... + def meta_tree_format(self, schema: Schema | None = None) -> str: ... + def meta_show_graph(self, schema: Schema | None = None) -> str: ... + def meta_replace_element(self, expr: PyExpr) -> PyExpr: ... + + # name + def name_keep(self) -> PyExpr: ... + def name_map(self, lambda_function: Any) -> PyExpr: ... + def name_prefix(self, prefix: str) -> PyExpr: ... + def name_suffix(self, suffix: str) -> PyExpr: ... + def name_to_lowercase(self) -> PyExpr: ... + def name_to_uppercase(self) -> PyExpr: ... + def name_map_fields(self, name_mapper: Any) -> PyExpr: ... + def name_prefix_fields(self, prefix: str) -> PyExpr: ... + def name_suffix_fields(self, suffix: str) -> PyExpr: ... + def name_replace(self, pattern: str, value: str, literal: bool) -> PyExpr: ... + + # rolling + def rolling_sum( + self, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_sum_by( + self, + by: PyExpr, + window_size: str, + min_periods: int, + closed: ClosedWindow, + ) -> PyExpr: ... + def rolling_min( + self, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_min_by( + self, + by: PyExpr, + window_size: str, + min_periods: int, + closed: ClosedWindow, + ) -> PyExpr: ... + def rolling_max( + self, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_max_by( + self, + by: PyExpr, + window_size: str, + min_periods: int, + closed: ClosedWindow, + ) -> PyExpr: ... + def rolling_mean( + self, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_mean_by( + self, + by: PyExpr, + window_size: str, + min_periods: int, + closed: ClosedWindow, + ) -> PyExpr: ... + def rolling_std( + self, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ddof: int = 1, + ) -> PyExpr: ... + def rolling_std_by( + self, + by: PyExpr, + window_size: str, + min_periods: int, + closed: ClosedWindow, + ddof: int = 1, + ) -> PyExpr: ... + def rolling_var( + self, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ddof: int = 1, + ) -> PyExpr: ... + def rolling_var_by( + self, + by: PyExpr, + window_size: str, + min_periods: int, + closed: ClosedWindow, + ddof: int = 1, + ) -> PyExpr: ... + def rolling_median( + self, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_median_by( + self, + by: PyExpr, + window_size: str, + min_periods: int, + closed: ClosedWindow, + ) -> PyExpr: ... + def rolling_quantile( + self, + quantile: float, + interpolation: QuantileMethod, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_quantile_by( + self, + by: PyExpr, + quantile: float, + interpolation: QuantileMethod, + window_size: str, + min_periods: int, + closed: ClosedWindow, + ) -> PyExpr: ... + def rolling_rank( + self, + window_size: int, + method: RankMethod, + seed: int | None = None, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_rank_by( + self, + by: PyExpr, + window_size: str, + method: RankMethod, + seed: int | None, + min_samples: int, + closed: ClosedWindow, + ) -> PyExpr: ... + def rolling_skew( + self, + window_size: int, + bias: bool, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_kurtosis( + self, + window_size: int, + fisher: bool, + bias: bool, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + def rolling_map( + self, + lambda_function: Any, + window_size: int, + weights: Sequence[float] | None = None, + min_periods: int | None = None, + center: bool = False, + ) -> PyExpr: ... + + # serde + def __getstate__(self) -> bytes: ... + def __setstate__(self, state: Any) -> None: ... + def serialize_binary(self, py_f: Any) -> None: ... + def serialize_json(self, py_f: Any) -> None: ... + @staticmethod + def deserialize_binary(py_f: Any) -> PyExpr: ... + @staticmethod + def deserialize_json(py_f: Any) -> PyExpr: ... + + # string + def str_join(self, delimiter: str, ignore_nulls: bool) -> PyExpr: ... + def str_to_date( + self, + format: str | None = None, + strict: bool = True, + exact: bool = True, + cache: bool = True, + ) -> PyExpr: ... + def str_to_datetime( + self, + format: str | None, + time_unit: TimeUnit | None, + time_zone: TimeZone | None, + strict: bool, + exact: bool, + cache: bool, + ambiguous: PyExpr, + ) -> PyExpr: ... + def str_to_time( + self, + format: str | None = None, + strict: bool = True, + cache: bool = True, + ) -> PyExpr: ... + def str_strip_chars(self, matches: PyExpr) -> PyExpr: ... + def str_strip_chars_start(self, matches: PyExpr) -> PyExpr: ... + def str_strip_chars_end(self, matches: PyExpr) -> PyExpr: ... + def str_strip_prefix(self, prefix: PyExpr) -> PyExpr: ... + def str_strip_suffix(self, suffix: PyExpr) -> PyExpr: ... + def str_slice(self, offset: PyExpr, length: PyExpr) -> PyExpr: ... + def str_head(self, n: PyExpr) -> PyExpr: ... + def str_tail(self, n: PyExpr) -> PyExpr: ... + def str_to_uppercase(self) -> PyExpr: ... + def str_to_lowercase(self) -> PyExpr: ... + def str_to_titlecase(self) -> PyExpr: ... + def str_len_bytes(self) -> PyExpr: ... + def str_len_chars(self) -> PyExpr: ... + def str_replace_n( + self, pat: PyExpr, val: PyExpr, literal: bool, n: int + ) -> PyExpr: ... + def str_replace_all(self, pat: PyExpr, val: PyExpr, literal: bool) -> PyExpr: ... + def str_normalize(self, form: UnicodeForm) -> PyExpr: ... + def str_reverse(self) -> PyExpr: ... + def str_pad_start(self, length: PyExpr, fill_char: str) -> PyExpr: ... + def str_pad_end(self, length: PyExpr, fill_char: str) -> PyExpr: ... + def str_zfill(self, length: PyExpr) -> PyExpr: ... + def str_contains( + self, pat: PyExpr, literal: bool | None = None, strict: bool = True + ) -> PyExpr: ... + def str_find( + self, pat: PyExpr, literal: bool | None = None, strict: bool = True + ) -> PyExpr: ... + def str_ends_with(self, sub: PyExpr) -> PyExpr: ... + def str_starts_with(self, sub: PyExpr) -> PyExpr: ... + def str_hex_encode(self) -> PyExpr: ... + def str_hex_decode(self, strict: bool) -> PyExpr: ... + def str_base64_encode(self) -> PyExpr: ... + def str_base64_decode(self, strict: bool) -> PyExpr: ... + def str_to_integer( + self, base: PyExpr, dtype: Any | None = None, strict: bool = True + ) -> PyExpr: ... + def str_json_decode( + self, dtype: PyDataTypeExpr | None = None, infer_schema_len: int | None = None + ) -> PyExpr: ... + def str_json_path_match(self, pat: PyExpr) -> PyExpr: ... + def str_extract(self, pat: PyExpr, group_index: int) -> PyExpr: ... + def str_extract_all(self, pat: PyExpr) -> PyExpr: ... + def str_extract_groups(self, pat: str) -> PyExpr: ... + def str_count_matches(self, pat: PyExpr, literal: bool) -> PyExpr: ... + def str_split(self, by: PyExpr) -> PyExpr: ... + def str_split_inclusive(self, by: PyExpr) -> PyExpr: ... + def str_split_exact(self, by: PyExpr, n: int) -> PyExpr: ... + def str_split_exact_inclusive(self, by: PyExpr, n: int) -> PyExpr: ... + def str_splitn(self, by: PyExpr, n: int) -> PyExpr: ... + def str_split_regex(self, by: PyExpr, strict: bool) -> PyExpr: ... + def str_split_regex_inclusive(self, by: PyExpr, strict: bool) -> PyExpr: ... + def str_to_decimal(self, scale: int) -> PyExpr: ... + def str_contains_any( + self, + patterns: PyExpr, + ascii_case_insensitive: bool, + ) -> PyExpr: ... + def str_replace_many( + self, + patterns: PyExpr, + replace_with: PyExpr, + ascii_case_insensitive: bool, + leftmost: bool, + ) -> PyExpr: ... + def str_extract_many( + self, + patterns: PyExpr, + ascii_case_insensitive: bool, + overlapping: bool, + leftmost: bool, + ) -> PyExpr: ... + def str_find_many( + self, + patterns: PyExpr, + ascii_case_insensitive: bool, + overlapping: bool, + leftmost: bool, + ) -> PyExpr: ... + def str_escape_regex(self) -> PyExpr: ... + @staticmethod + def str_format(f_string: str, exprs: pylist[PyExpr]) -> PyExpr: ... + + # struct + def struct_field_by_index(self, index: int) -> PyExpr: ... + def struct_field_by_name(self, name: str) -> PyExpr: ... + def struct_multiple_fields(self, names: Sequence[str]) -> PyExpr: ... + def struct_rename_fields(self, names: Sequence[str]) -> PyExpr: ... + def struct_drop(self, names: Sequence[str], strict: bool) -> PyExpr: ... + def struct_json_encode(self) -> PyExpr: ... + def struct_with_fields(self, fields: Sequence[PyExpr]) -> PyExpr: ... + def struct_eval(self, fields: Sequence[PyExpr]) -> PyExpr: ... + + # map + def map_entries(self) -> PyExpr: ... + def map_keys(self) -> PyExpr: ... + def map_values(self) -> PyExpr: ... + def map_len(self) -> PyExpr: ... + def map_contains_key(self, key: PyExpr) -> PyExpr: ... + def map_get(self, key: PyExpr) -> PyExpr: ... + + # extension + def ext_to(self, dtype: PyDataTypeExpr) -> PyExpr: ... + def ext_storage(self) -> PyExpr: ... + +class PyDataTypeExpr: + def __init__(self, inner: Any) -> None: ... + @staticmethod + def from_dtype(datatype: Any) -> PyDataTypeExpr: ... + @staticmethod + def of_expr(expr: PyExpr) -> PyDataTypeExpr: ... + @staticmethod + def self_dtype() -> PyDataTypeExpr: ... + def collect_dtype(self, schema: Any) -> Any: ... + def inner_dtype(self) -> PyDataTypeExpr: ... + def equals(self, other: PyDataTypeExpr) -> PyExpr: ... + def display(self) -> PyExpr: ... + def matches(self, selector: Any) -> PyExpr: ... + @staticmethod + def struct_with_fields( + fields: Sequence[tuple[str, PyDataTypeExpr]], + ) -> PyDataTypeExpr: ... + def wrap_in_list(self) -> PyDataTypeExpr: ... + def wrap_in_array(self, width: int) -> PyDataTypeExpr: ... + def to_unsigned_integer(self) -> PyDataTypeExpr: ... + def to_signed_integer(self) -> PyDataTypeExpr: ... + def default_value( + self, n: int, numeric_to_one: bool, num_list_values: int + ) -> PyExpr: ... + + # list + def list_inner_dtype(self) -> PyDataTypeExpr: ... + + # array + def arr_inner_dtype(self) -> PyDataTypeExpr: ... + def arr_width(self) -> PyExpr: ... + def arr_shape(self) -> PyExpr: ... + + # struct + def struct_field_dtype_by_index(self, index: int) -> PyDataTypeExpr: ... + def struct_field_dtype_by_name(self, name: str) -> PyDataTypeExpr: ... + def struct_field_names(self) -> PyExpr: ... + +class PySelector: + def __init__(self, inner: Any) -> None: ... + def union(self, other: PySelector) -> PySelector: ... + def difference(self, other: PySelector) -> PySelector: ... + def exclusive_or(self, other: PySelector) -> PySelector: ... + def intersect(self, other: PySelector) -> PySelector: ... + @staticmethod + def by_dtype(dtypes: Sequence[Any]) -> PySelector: ... + @staticmethod + def by_name( + names: Sequence[str], strict: bool, expand_patterns: bool + ) -> PySelector: ... + @staticmethod + def by_index(indices: Sequence[int], strict: bool) -> PySelector: ... + @staticmethod + def first(strict: bool) -> PySelector: ... + @staticmethod + def last(strict: bool) -> PySelector: ... + @staticmethod + def matches(pattern: str) -> PySelector: ... + @staticmethod + def enum_() -> PySelector: ... + @staticmethod + def categorical() -> PySelector: ... + @staticmethod + def nested() -> PySelector: ... + @staticmethod + def list(inner_dst: PySelector | None) -> PySelector: ... + @staticmethod + def array(inner_dst: PySelector | None, width: int | None) -> PySelector: ... + @staticmethod + def struct_() -> PySelector: ... + @staticmethod + def map() -> PySelector: ... + @staticmethod + def integer() -> PySelector: ... + @staticmethod + def signed_integer() -> PySelector: ... + @staticmethod + def unsigned_integer() -> PySelector: ... + @staticmethod + def float() -> PySelector: ... + @staticmethod + def decimal() -> PySelector: ... + @staticmethod + def numeric() -> PySelector: ... + @staticmethod + def temporal() -> PySelector: ... + @staticmethod + def datetime(tu: Sequence[Any], tz: Sequence[Any]) -> PySelector: ... + @staticmethod + def duration(tu: Sequence[Any]) -> PySelector: ... + @staticmethod + def object() -> PySelector: ... + @staticmethod + def empty() -> PySelector: ... + @staticmethod + def all() -> PySelector: ... + def hash(self) -> int: ... + +class PyFileDecryptionProperties: + def __init__( + self, + footer_key: bytes, + column_keys: pylist[tuple[str, bytes]], + aad_prefix: bytes | None, + verify_footer_signature: bool, + ) -> None: ... + +class PyFileEncryptionProperties: + def __init__( + self, + footer_key: bytes, + column_keys: pylist[tuple[str, bytes]], + plaintext_footer: bool, + aad_prefix: bytes | None, + store_aad_prefix: bool, + ) -> None: ... + +class PyOptFlags: + def __init__(self) -> None: ... + @staticmethod + def empty() -> PyOptFlags: ... + @staticmethod + def default() -> PyOptFlags: ... + def no_optimizations(self) -> None: ... + def copy(self) -> PyOptFlags: ... + @property + def type_coercion(self) -> bool: ... + @type_coercion.setter + def type_coercion(self, value: bool) -> None: ... + @property + def type_check(self) -> bool: ... + @type_check.setter + def type_check(self, value: bool) -> None: ... + @property + def projection_pushdown(self) -> bool: ... + @projection_pushdown.setter + def projection_pushdown(self, value: bool) -> None: ... + @property + def predicate_pushdown(self) -> bool: ... + @predicate_pushdown.setter + def predicate_pushdown(self, value: bool) -> None: ... + @property + def cluster_with_columns(self) -> bool: ... + @cluster_with_columns.setter + def cluster_with_columns(self, value: bool) -> None: ... + @property + def simplify_expression(self) -> bool: ... + @simplify_expression.setter + def simplify_expression(self, value: bool) -> None: ... + @property + def slice_pushdown(self) -> bool: ... + @slice_pushdown.setter + def slice_pushdown(self, value: bool) -> None: ... + @property + def comm_subplan_elim(self) -> bool: ... + @comm_subplan_elim.setter + def comm_subplan_elim(self, value: bool) -> None: ... + @property + def comm_subexpr_elim(self) -> bool: ... + @comm_subexpr_elim.setter + def comm_subexpr_elim(self, value: bool) -> None: ... + @property + def check_order_observe(self) -> bool: ... + @check_order_observe.setter + def check_order_observe(self, value: bool) -> None: ... + @property + def fast_projection(self) -> bool: ... + @fast_projection.setter + def fast_projection(self, value: bool) -> None: ... + @property + def sort_collapse(self) -> bool: ... + @sort_collapse.setter + def sort_collapse(self, value: bool) -> None: ... + @property + def join_order(self) -> bool: ... + @join_order.setter + def join_order(self, value: bool) -> None: ... + @property + def row_estimate(self) -> bool: ... + @row_estimate.setter + def row_estimate(self, value: bool) -> None: ... + @property + def pre_partition_hive(self) -> bool: ... + @pre_partition_hive.setter + def pre_partition_hive(self, value: bool) -> None: ... + @property + def eager(self) -> bool: ... + @eager.setter + def eager(self, value: bool) -> None: ... + @property + def streaming(self) -> bool: ... + @streaming.setter + def streaming(self, value: bool) -> None: ... + @property + def query_monitoring(self) -> bool: ... + @query_monitoring.setter + def query_monitoring(self, value: bool) -> None: ... + +# functions.lazy +def rolling_corr( + x: PyExpr, y: PyExpr, window_size: int, min_periods: int +) -> PyExpr: ... +def rolling_cov( + x: PyExpr, y: PyExpr, window_size: int, min_periods: int, ddof: int +) -> PyExpr: ... +def arg_sort_by( + by: Sequence[PyExpr], + descending: Sequence[bool], + nulls_last: Sequence[bool], + multithreaded: bool, + maintain_order: bool, +) -> PyExpr: ... +def arg_where(condition: PyExpr) -> PyExpr: ... +def as_struct(exprs: Sequence[PyExpr]) -> PyExpr: ... +def field(names: Sequence[str]) -> PyExpr: ... +def coalesce(exprs: Sequence[PyExpr]) -> PyExpr: ... +def col(name: str) -> PyExpr: ... +def element() -> PyExpr: ... +def collect_all( + lfs: Sequence[PyLazyFrame], engine: Any, optflags: PyOptFlags +) -> pylist[PyDataFrame]: ... +def explain_all(lfs: Sequence[PyLazyFrame], optflags: PyOptFlags) -> str: ... +def collect_all_lazy( + lfs: Sequence[PyLazyFrame], optflags: PyOptFlags +) -> PyLazyFrame: ... +def collect_all_with_callback( + lfs: Sequence[PyLazyFrame], engine: Any, optflags: PyOptFlags, lambda_func: Any +) -> None: ... +def concat_lf( + seq: Any, rechunk: bool, parallel: bool, to_supertypes: bool, maintain_order: bool +) -> PyLazyFrame: ... +def concat_list(s: Sequence[PyExpr]) -> PyExpr: ... +def list(s: Sequence[PyExpr]) -> PyExpr: ... +def concat_arr(s: Sequence[PyExpr]) -> PyExpr: ... +def concat_str(s: Sequence[PyExpr], separator: str, ignore_nulls: bool) -> PyExpr: ... +def len() -> PyExpr: ... +def cov(a: PyExpr, b: PyExpr, ddof: int) -> PyExpr: ... +def arctan2(y: PyExpr, x: PyExpr) -> PyExpr: ... +def cum_fold( + acc: PyExpr, + lambda_func: Any, + exprs: Sequence[PyExpr], + returns_scalar: bool, + return_dtype: PyDataTypeExpr | None, + include_init: bool, +) -> PyExpr: ... +def cum_reduce( + lambda_func: Any, + exprs: Sequence[PyExpr], + returns_scalar: bool, + return_dtype: PyDataTypeExpr | None, +) -> PyExpr: ... +def datetime( + year: PyExpr, + month: PyExpr, + day: PyExpr, + hour: PyExpr | None, + minute: PyExpr | None, + second: PyExpr | None, + microsecond: PyExpr | None, + time_unit: TimeUnit, # Default set by Rust code + time_zone: TimeZone | None, # Default set by Rust code + ambiguous: PyExpr, # Default set by Rust code +) -> PyExpr: ... +def concat_lf_diagonal( + lfs: Any, rechunk: bool, parallel: bool, to_supertypes: bool, maintain_order: bool +) -> PyLazyFrame: ... +def concat_lf_horizontal( + lfs: Any, + parallel: bool, + strict: bool = False, +) -> PyLazyFrame: ... +def concat_expr(e: Sequence[PyExpr], rechunk: bool) -> PyExpr: ... +def duration( + weeks: PyExpr | None, + days: PyExpr | None, + hours: PyExpr | None, + minutes: PyExpr | None, + seconds: PyExpr | None, + milliseconds: PyExpr | None, + microseconds: PyExpr | None, + nanoseconds: PyExpr | None, + time_unit: TimeUnit, # Default set by Rust code +) -> PyExpr: ... +def fold( + acc: PyExpr, + lambda_func: Any, + exprs: Sequence[PyExpr], + returns_scalar: bool, + return_dtype: PyDataTypeExpr | None, +) -> PyExpr: ... +def lit(value: Any, allow_object: bool, is_scalar: bool) -> PyExpr: ... +def map_expr( + pyexpr: Sequence[PyExpr], + lambda_func: Any, + output_type: PyDataTypeExpr | None, + is_elementwise: bool, + returns_scalar: bool, +) -> PyExpr: ... +def pipe_with_dtype( + exprs: Sequence[PyExpr], + callback: Callable[[tuple[pylist[PyExpr], pylist[DataType]]], PyExpr], +) -> PyExpr: ... +def pearson_corr(a: PyExpr, b: PyExpr) -> PyExpr: ... +def reduce( + lambda_func: Any, + exprs: Sequence[PyExpr], + returns_scalar: bool, + return_dtype: PyDataTypeExpr | None, +) -> PyExpr: ... +def repeat(value: PyExpr, n: PyExpr, dtype: Any | None = None) -> PyExpr: ... +def spearman_rank_corr(a: PyExpr, b: PyExpr, propagate_nans: bool) -> PyExpr: ... +def sql_expr(sql: str) -> PyExpr: ... + +# functions.aggregations +def all_horizontal(exprs: Sequence[PyExpr]) -> PyExpr: ... +def any_horizontal(exprs: Sequence[PyExpr]) -> PyExpr: ... +def max_horizontal(exprs: Sequence[PyExpr]) -> PyExpr: ... +def min_horizontal(exprs: Sequence[PyExpr]) -> PyExpr: ... +def sum_horizontal(exprs: Sequence[PyExpr], ignore_nulls: bool) -> PyExpr: ... +def mean_horizontal(exprs: Sequence[PyExpr], ignore_nulls: bool) -> PyExpr: ... + +# functions.business +def business_day_count( + start: PyExpr, + end: PyExpr, + week_mask: Sequence[bool], + holidays: PyExpr, +) -> PyExpr: ... + +# functions.eager +def concat_df(dfs: Any) -> PyDataFrame: ... +def concat_series(series: Any) -> PySeries: ... +def concat_df_diagonal(dfs: Any) -> PyDataFrame: ... +def concat_df_horizontal(dfs: Any, strict: bool = False) -> PyDataFrame: ... + +# functions.io +def read_ipc_schema(py_f: Any) -> dict[str, Any]: ... +def read_parquet_metadata( + py_f: Any, storage_options: Any, credential_provider: Any +) -> dict[str, str]: ... +def _bench_parquet_metadata_bincode_size( + path: str, projection: pylist[str] | None, predicate: pylist[str] +) -> int: ... +def _parquet_metadata_pruned_json( + path: str, projection: pylist[str], predicate: pylist[str] +) -> str: ... + +# I/O plugin IDs (capsule names) this build can call. +_IO_PLUGIN_IDS: pylist[str] + +def _iceberg_plugin_scan( + capsule: Any, + *, + metadata_location: str, + snapshot_id: int | None, + from_snapshot_id_exclusive: int | None, + to_snapshot_id_inclusive: int | None, + projection: pylist[str] | None, + filter_columns: pylist[str] | None, + row_filter: str | None, + limit: int | None, + use_metadata_statistics: bool, + fast_deletion_count: bool, + verbose: bool, + testing_fail: str | None, + source_url: str, + storage_options: Any, + credential_provider: Any, + cast_options: Any, + metadata_cache: PyIcebergMetadataFileCache | None, + metadata_cache_scope: str | None, +) -> PyLazyFrame: ... + +class PyIcebergMetadataFileCache: + def __init__(self, max_bytes: int) -> None: ... + @property + def total_bytes(self) -> int: ... + def __len__(self) -> int: ... + +def read_clipboard_string() -> str: ... +def write_clipboard_string(s: str) -> None: ... + +# functions.meta +def get_index_type() -> Any: ... +def thread_pool_size() -> int: ... +def set_float_fmt(fmt: FloatFmt) -> None: ... +def get_float_fmt() -> str: ... +def set_float_precision(precision: int | None) -> None: ... +def get_float_precision() -> int | None: ... +def set_thousands_separator(sep: str | None) -> None: ... +def get_thousands_separator() -> str | None: ... +def set_decimal_separator(sep: str | None) -> None: ... +def get_decimal_separator() -> str | None: ... +def set_trim_decimal_zeros(trim: bool | None) -> None: ... +def get_trim_decimal_zeros() -> bool | None: ... + +# functions.misc +def dtype_str_repr(dtype: Any) -> str: ... +def register_plugin_function( + plugin_path: str, + function_name: str, + args: Sequence[PyExpr], + kwargs: Sequence[int], + is_elementwise: bool, + input_wildcard_expansion: bool, + returns_scalar: bool, + cast_to_supertype: bool, + pass_name_to_apply: bool, + changes_length: bool, + is_deterministic: bool, +) -> PyExpr: ... +def __register_startup_deps(warn_function: Callable[..., None]) -> None: ... +def gen_uuid_v7() -> bytes: ... + +# functions.random +def set_random_seed(seed: int) -> None: ... + +# functions.range +def int_range( + start: PyExpr, end: PyExpr, step: int, dtype: PyDataTypeExpr +) -> PyExpr: ... +def eager_int_range( + lower: Any, upper: Any, step: Any, dtype: PyDataTypeExpr +) -> PySeries: ... +def int_ranges( + start: PyExpr, end: PyExpr, step: PyExpr, dtype: PyDataTypeExpr +) -> PyExpr: ... +def date_range( + start: PyExpr, end: PyExpr, interval: str, closed: ClosedWindow +) -> PyExpr: ... +def date_ranges( + start: PyExpr, end: PyExpr, interval: str, closed: ClosedWindow +) -> PyExpr: ... +def datetime_range( + start: PyExpr, + end: PyExpr, + every: str, + closed: ClosedWindow, + time_unit: TimeUnit | None, + time_zone: TimeZone | None, +) -> PyExpr: ... +def datetime_ranges( + start: PyExpr, + end: PyExpr, + every: str, + closed: ClosedWindow, + time_unit: TimeUnit | None, + time_zone: TimeZone | None, +) -> PyExpr: ... +def time_range( + start: PyExpr, end: PyExpr, every: str, closed: ClosedWindow +) -> PyExpr: ... +def time_ranges( + start: PyExpr, end: PyExpr, every: str, closed: ClosedWindow +) -> PyExpr: ... +def linear_space( + start: PyExpr, end: PyExpr, num_samples: PyExpr, closed: ClosedInterval +) -> PyExpr: ... +def linear_spaces( + start: PyExpr, + end: PyExpr, + num_samples: PyExpr, + closed: ClosedInterval, + as_array: bool, +) -> PyExpr: ... + +# functions.strings +def escape_regex(s: str) -> str: ... + +# functions: other +def check_length(check: bool) -> None: ... +def get_engine_affinity() -> EngineType: ... +def config_reload_env_vars() -> None: ... +def config_reload_env_var(var: str) -> None: ... +def set_query_monitoring( + enable: bool, workspace: str | None = None, organization: str | None = None +) -> None: ... + +# functions.when +class PyWhen: + def then(self, statement: PyExpr) -> PyThen: ... + +class PyThen: + def when(self, condition: PyExpr) -> PyChainedWhen: ... + def otherwise(self, statement: PyExpr) -> PyExpr: ... + def __getstate__(self) -> bytes: ... + def __setstate__(self, state: bytes) -> None: ... + +class PyChainedWhen: + def then(self, statement: PyExpr) -> PyChainedThen: ... + +class PyChainedThen: + def when(self, condition: PyExpr) -> PyChainedWhen: ... + def otherwise(self, statement: PyExpr) -> PyExpr: ... + def __getstate__(self) -> bytes: ... + def __setstate__(self, state: bytes) -> None: ... + +def when(condition: PyExpr) -> PyWhen: ... + +# functions: schema +def init_polars_schema_from_arrow_c_schema( + polars_schema: Any, schema_object: Any +) -> None: ... +def polars_schema_field_from_arrow_c_schema(schema_object: Any) -> tuple[str, Any]: ... +def polars_schema_to_pycapsule(schema: Schema, compat_level: CompatLevel) -> Any: ... + +class PyLazyGroupBy: + def agg(self, aggs: pylist[PyExpr]) -> PyLazyFrame: ... + def head(self, n: int) -> PyLazyFrame: ... + def tail(self, n: int) -> PyLazyFrame: ... + def having(self, predicates: pylist[PyExpr]) -> PyLazyGroupBy: ... + def map_groups( + self, lambda_function: Any, schema: Schema | None + ) -> PyLazyFrame: ... + +# categorical +class PyCategories: + def __init__(self, name: str, namespace: str, physical: str) -> None: ... + @staticmethod + def global_categories() -> PyCategories: ... + @staticmethod + def random(namespace: str, physical: str) -> PyCategories: ... + def __eq__(self, other: PyCategories) -> bool: ... # type: ignore[override] + def __hash__(self) -> int: ... + def name(self) -> str: ... + def namespace(self) -> str: ... + def physical(self) -> str: ... + def get_cat(self, s: str) -> int | None: ... + def cat_to_str(self, cat: int) -> str | None: ... + def num_cats_upper_bound(self) -> int: ... + def is_global(self) -> bool: ... + +# arrow c stream +class PyArrowCStreamReader: + def __init__(self, source: Any) -> None: ... + @property + def schema(self) -> dict[str, Any]: ... + def next_batch(self, with_columns: pylist[str] | None) -> PyDataFrame | None: ... + +# catalog +class PyCatalogClient: + @staticmethod + def new(workspace_url: str, bearer_token: str | None) -> PyCatalogClient: ... + def list_catalogs(self) -> pylist[Any]: ... + def list_namespaces(self, catalog_name: str) -> pylist[Any]: ... + def list_tables(self, catalog_name: str, namespace: str) -> pylist[Any]: ... + def get_table_info( + self, table_name: str, catalog_name: str, namespace: str + ) -> Any: ... + def get_table_credentials( + self, table_id: str, write: bool + ) -> tuple[Any, Any, Any]: ... + def scan_table( + self, + catalog_name: str, + namespace: str, + table_name: str, + cloud_options: dict[str, str] | None, + credential_provider: Any | None, + ) -> PyLazyFrame: ... + def create_catalog( + self, catalog_name: str, comment: str | None, storage_root: str | None + ) -> Any: ... + def delete_catalog(self, catalog_name: str, force: bool) -> None: ... + def create_namespace( + self, + catalog_name: str, + namespace: str, + comment: str | None, + storage_root: str | None, + ) -> Any: ... + def delete_namespace( + self, catalog_name: str, namespace: str, force: bool + ) -> None: ... + def create_table( + self, + catalog_name: str, + namespace: str, + table_name: str, + schema: Any | None, + table_type: str, + data_source_format: str | None, + comment: str | None, + storage_root: str | None, + properties: Sequence[tuple[str, str]], + ) -> Any: ... + def delete_table( + self, catalog_name: str, namespace: str, table_name: str + ) -> None: ... + @staticmethod + def type_json_to_polars_type(type_json: str) -> Any: ... + @staticmethod + def init_classes( + catalog_info_cls: Any, + namespace_info_cls: Any, + table_info_cls: Any, + column_info_cls: Any, + ) -> None: ... + +# sql +class PySQLContext: + @staticmethod + def new() -> PySQLContext: ... + def execute(self, query: str) -> PyLazyFrame: ... + def get_tables(self) -> pylist[str]: ... + def register(self, name: str, lf: PyLazyFrame) -> None: ... + def unregister(self, name: str) -> None: ... + def register_catalog( + self, name: str, default_namespace: pylist[str] | None, default: bool + ) -> None: ... + def default_catalog(self) -> str | None: ... + def set_default_catalog(self, name: str) -> None: ... + def unregister_catalog(self, name: str) -> str | None: ... + def catalog_tables(self, query: str) -> pylist[tuple[str, pylist[str]]]: ... + def catalog_ddl(self, query: str) -> dict[str, Any] | None: ... + def execute_with_catalog_tables( + self, query: str, tables: pylist[tuple[pylist[str], PyLazyFrame]] + ) -> PyLazyFrame: ... + @staticmethod + def table_identifiers( + query: str, + include_schema: bool = ..., + unique: bool = ..., + ) -> pylist[str]: ... + +# testing +def assert_series_equal_py( + left: PySeries, + right: PySeries, + *, + check_dtypes: bool, + check_names: bool, + check_order: bool, + check_exact: bool, + rel_tol: float, + abs_tol: float, + categorical_as_str: bool, +) -> None: ... +def assert_dataframe_equal_py( + left: PyDataFrame, + right: PyDataFrame, + *, + check_row_order: bool, + check_column_order: bool, + check_dtypes: bool, + check_exact: bool, + rel_tol: float, + abs_tol: float, + categorical_as_str: bool, +) -> None: ... +def assert_schema_equal_py( + left: Schema, + right: Schema, + *, + check_column_order: bool, + check_dtypes: bool, +) -> None: ... + +# datatypes +def _get_dtype_max(dt: DataType) -> PyExpr: ... +def _get_dtype_min(dt: DataType) -> PyExpr: ... +def _known_timezones() -> pylist[str]: ... + +# extension +def _register_extension_type(name: str, cls: Any | None) -> None: ... +def _unregister_extension_type(name: str) -> None: ... + +# cloud_client +def prepare_cloud_plan( + lf: PyLazyFrame, +) -> bytes: ... + +# cloud_server +def _execute_ir_plan_with_gpu(ir_plan_ser: Sequence[int]) -> PyDataFrame: ... + +# visit +class PyExprIR: + node: int + output_name: str + +class NodeTraverser: + def get_exprs(self) -> pylist[PyExprIR]: ... + def get_inputs(self) -> pylist[int]: ... + def version(self) -> tuple[int, int]: ... + def get_schema(self) -> dict[str, DataType]: ... + def get_dtype(self, expr_node: int) -> DataType: ... + def set_node(self, node: int) -> None: ... + def get_node(self) -> int: ... + def set_udf(self, function: Any, is_pure: bool = False) -> None: ... + def view_current_node(self) -> Any: ... + def view_expression(self, node: int) -> Any: ... + def add_expressions( + self, expressions: pylist[PyExpr] + ) -> tuple[pylist[int], int]: ... + def set_expr_mapping(self, mapping: pylist[int]) -> None: ... + def unset_expr_mapping(self) -> None: ... + +class PyCollectBatches: + def start(self) -> None: ... + def __iter__(self) -> PyCollectBatches: ... + def __next__(self) -> PyDataFrame: ... + + # Export + def __arrow_c_stream__(self, requested_schema: object | None = None) -> object: ... diff --git a/hug/lib/python3.12/site-packages/polars/_reexport.py b/hug/lib/python3.12/site-packages/polars/_reexport.py new file mode 100644 index 0000000000000000000000000000000000000000..7a7a092904af7fc5c313fb6e14912935e5872bb9 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_reexport.py @@ -0,0 +1,23 @@ +"""Re-export Polars functionality to avoid cyclical imports.""" + +from polars.dataframe import DataFrame +from polars.datatype_expr import DataTypeExpr +from polars.datatypes import DataType, DataTypeClass +from polars.expr import Expr, When +from polars.lazyframe import LazyFrame +from polars.schema import Schema +from polars.selectors import Selector +from polars.series import Series + +__all__ = [ + "DataFrame", + "DataTypeExpr", + "DataType", + "DataTypeClass", + "Expr", + "LazyFrame", + "Schema", + "Selector", + "Series", + "When", +] diff --git a/hug/lib/python3.12/site-packages/polars/_typing.py b/hug/lib/python3.12/site-packages/polars/_typing.py new file mode 100644 index 0000000000000000000000000000000000000000..098cc3bc1c145b4785eddbd8d6c2c877c5eec98f --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_typing.py @@ -0,0 +1,605 @@ +from __future__ import annotations + +from collections.abc import Callable, Collection, Iterable, Mapping, Sequence +from pathlib import Path +from typing import ( + IO, + TYPE_CHECKING, + Any, + Literal, + Protocol, + TypeVar, + Union, +) + +if TYPE_CHECKING: + from datetime import date, datetime, time, timedelta + from decimal import Decimal + from typing import TypeAlias + + from sqlalchemy.engine import Connection + from sqlalchemy.engine import Engine as AlchemyEngine + from sqlalchemy.ext.asyncio import ( + AsyncConnection, + AsyncEngine, + AsyncSession, + async_sessionmaker, + ) + from sqlalchemy.orm import Session + from xlsxwriter.format import Format + + from polars import DataFrame, Expr, LazyFrame, Series + from polars._dependencies import numpy as np + from polars._utils.async_ import _AioDataFrameResult, _GeventDataFrameResult + from polars.datatypes import DataType, DataTypeClass, IntegerType, TemporalType + from polars.lazyframe.engine import Engine + from polars.selectors import Selector + + +class ArrowArrayExportable(Protocol): + """Type protocol for Arrow C Data Interface via Arrow PyCapsule Interface.""" + + def __arrow_c_array__( + self, requested_schema: object | None = None + ) -> tuple[object, object]: ... + + +class ArrowStreamExportable(Protocol): + """Type protocol for Arrow C Stream Interface via Arrow PyCapsule Interface.""" + + def __arrow_c_stream__(self, requested_schema: object | None = None) -> object: ... + + +class ArrowSchemaExportable(Protocol): + """Type protocol for Arrow C Schema Interface via Arrow PyCapsule Interface.""" + + def __arrow_c_schema__(self) -> object: ... + + +class NumpyArray(Protocol): + """Protocol to match NumPy Arrays without needing NumPy installed.""" + + def byteswap(self, *args: Any, **kwargs: Any) -> Any: ... + def conjugate(self, *args: Any, **kwargs: Any) -> Any: ... + def ravel(self, *args: Any, **kwargs: Any) -> Any: ... + def searchsorted(self, *args: Any, **kwargs: Any) -> Any: ... + def swapaxes(self, *args: Any, **kwargs: Any) -> Any: ... + + +class PyArrowArray(Protocol): + """ + Protocol to match PyArrow arrays without needing PyArrow installed. + + Only use for function arguments, not return types. + """ + + def buffers(self, *args: Any, **kwargs: Any) -> Any: ... + def tolist(self, *args: Any, **kwargs: Any) -> Any: ... + + +class PyArrowChunkedArray(Protocol): + """ + Protocol to match PyArrow chunked arrays without needing PyArrow installed. + + Only use for function arguments, not return types. + """ + + def iterchunks(self, *args: Any, **kwargs: Any) -> Any: ... + + +class PyArrowTable(Protocol): + """ + Protocol to match PyArrow tables without needing PyArrow installed. + + Only use for function arguments, not return types. + """ + + def filter(self, *args: Any, **kwargs: Any) -> Any: ... + def group_by(self, *args: Any, **kwargs: Any) -> Any: ... + def add_column(self, *args: Any, **kwargs: Any) -> Any: ... + def remove_column(self, *args: Any, **kwargs: Any) -> Any: ... + def take(self, *args: Any, **kwargs: Any) -> Any: ... + def to_pandas(self, *args: Any, **kwargs: Any) -> Any: ... + + +class PandasDataFrame(Protocol): + """ + Protocol to match pandas dataframes without needing pandas-stubs installed. + + Only use for function arguments, not return types. + """ + + def where(self, *args: Any, **kwargs: Any) -> Any: ... + def groupby(self, *args: Any, **kwargs: Any) -> Any: ... + def unstack(self, *args: Any, **kwargs: Any) -> Any: ... + def pivot_table(self, *args: Any, **kwargs: Any) -> Any: ... + + +class PandasSeries(Protocol): + """ + Protocol to match pandas series without needing pandas-stubs installed. + + Only use for function arguments, not return types. + """ + + def to_frame(self, *args: Any, **kwargs: Any) -> Any: ... + def isna(self, *args: Any, **kwargs: Any) -> Any: ... + def rename_axis(self, *args: Any, **kwargs: Any) -> Any: ... + + +class PandasIndex(Protocol): + """ + Protocol to match pandas indexes without needing pandas-stubs installed. + + Only use for function arguments, not return types. + """ + + def to_series(self, *args: Any, **kwargs: Any) -> Any: ... + def isna(self, *args: Any, **kwargs: Any) -> Any: ... + + +class TorchTensor(Protocol): + """ + Protocol to match PyTorch tensors without needing PyTorch installed. + + Only use for function arguments, not return types. + """ + + def cuda(self, *args: Any, **kwargs: Any) -> Any: ... + def backward(self, *args: Any, **kwargs: Any) -> Any: ... + + +# Data types +PolarsDataType: TypeAlias = Union["DataTypeClass", "DataType"] +PolarsTemporalType: TypeAlias = Union[type["TemporalType"], "TemporalType"] +PolarsIntegerType: TypeAlias = Union[type["IntegerType"], "IntegerType"] +OneOrMoreDataTypes: TypeAlias = PolarsDataType | Iterable[PolarsDataType] +PythonDataType: TypeAlias = ( + type[int] + | type[float] + | type[bool] + | type[str] + | type["date"] + | type["time"] + | type["datetime"] + | type["timedelta"] + | type[list[Any]] + | type[tuple[Any, ...]] + | type[bytes] + | type[object] + | type["Decimal"] + | type[None] +) + +SchemaDefinition: TypeAlias = ( + Mapping[str, PolarsDataType | PythonDataType | None] + | Sequence[str | tuple[str, PolarsDataType | PythonDataType | None]] +) +SchemaDict: TypeAlias = Mapping[str, PolarsDataType] + +NumericLiteral: TypeAlias = Union[int, float, "Decimal"] +TemporalLiteral: TypeAlias = Union["date", "time", "datetime", "timedelta"] +NonNestedLiteral: TypeAlias = NumericLiteral | TemporalLiteral | str | bool | bytes +# Python literal types (can convert into a `lit` expression) +PythonLiteral: TypeAlias = Union[NonNestedLiteral, "np.ndarray[Any, Any]", list[Any]] +# Inputs that can convert into a `col` expression +IntoExprColumn: TypeAlias = Union["Expr", "Series", str] +# Inputs that can convert into an expression +IntoExpr: TypeAlias = PythonLiteral | IntoExprColumn | None + +ComparisonOperator: TypeAlias = Literal["eq", "neq", "gt", "lt", "gt_eq", "lt_eq"] +Alignment: TypeAlias = Literal["left", "center", "right", "LEFT", "CENTER", "RIGHT"] + +# selector type, and related collection/sequence +SelectorType: TypeAlias = "Selector" +ColumnNameOrSelector: TypeAlias = Union["str", SelectorType] + +# User-facing string literal types +# The following all have an equivalent Rust enum with the same name +Ambiguous: TypeAlias = Literal["earliest", "latest", "raise", "null"] +AvroCompression: TypeAlias = Literal["uncompressed", "snappy", "deflate"] +CsvQuoteStyle: TypeAlias = Literal["necessary", "always", "non_numeric", "never"] +CategoricalOrdering: TypeAlias = Literal["physical", "lexical"] +CsvCompression: TypeAlias = Literal["uncompressed", "gzip", "zstd"] +CsvEncoding: TypeAlias = Literal["utf8", "utf8-lossy"] +ColumnMapping: TypeAlias = ( + tuple[ + Literal["iceberg-column-mapping"], + # This is "pa.Schema". Not typed as that causes pyright strict type checking + # failures for users who don't have pyarrow-stubs installed. + Any, + ] + | tuple[ + Literal["iceberg-column-mapping"], + Any, + # Names of the table's name mapping, by field ID. + dict[int, list[str]] | None, + ] +) +DefaultFieldValues: TypeAlias = tuple[ + Literal["iceberg"], + # (identity-partition values, initial defaults[, identity-partition presence]) + tuple[dict[int, Union["Series", str]], dict[int, "Series"]] + | tuple[dict[int, Union["Series", str]], dict[int, "Series"], dict[int, "Series"]], +] +DeletionFiles: TypeAlias = ( + tuple[Literal["iceberg"], tuple[dict[int, list[str]], dict[int, str]]] + | tuple[Literal["delta-deletion-vector"], Callable[["DataFrame"], "DataFrame"]] +) +FillNullStrategy: TypeAlias = Literal[ + "forward", "backward", "min", "max", "mean", "zero", "one" +] +FloatFmt: TypeAlias = Literal["full", "mixed"] +IndexOrder: TypeAlias = Literal["c", "fortran"] +IpcCompression: TypeAlias = Literal["uncompressed", "lz4", "zstd"] +JoinValidation: TypeAlias = Literal["m:m", "m:1", "1:m", "1:1"] +Label: TypeAlias = Literal["left", "right", "datapoint"] +MaintainOrderJoin: TypeAlias = Literal[ + "none", "left", "right", "left_right", "right_left" +] +JoinBuildSide: TypeAlias = Literal[ + "auto", "prefer_left", "prefer_right", "force_left", "force_right" +] +NdjsonCompression: TypeAlias = Literal["uncompressed", "gzip", "zstd"] +NonExistent: TypeAlias = Literal["raise", "null"] +NullBehavior: TypeAlias = Literal["ignore", "drop"] +ParallelStrategy: TypeAlias = Literal[ + "auto", "columns", "row_groups", "prefiltered", "none" +] +ParquetCompression: TypeAlias = Literal[ + "lz4", "uncompressed", "snappy", "gzip", "brotli", "zstd" +] +PivotAgg: TypeAlias = Literal[ + "min", "max", "first", "last", "sum", "mean", "median", "len", "item" +] +QuantileMethod: TypeAlias = Literal[ + "nearest", "higher", "lower", "midpoint", "linear", "equiprobable" +] +ApproxQuantileMethod: TypeAlias = Literal["auto", "kll", "req_lo", "req_hi", "req_both"] +ApproxQuantileErrorBound: TypeAlias = Literal["empirical", "formal"] +RankMethod: TypeAlias = Literal["average", "min", "max", "dense", "ordinal", "random"] +Roll: TypeAlias = Literal["raise", "forward", "backward"] +RoundMode: TypeAlias = Literal["half_to_even", "half_away_from_zero", "to_zero"] +SerializationFormat: TypeAlias = Literal["binary", "json"] +Endianness: TypeAlias = Literal["little", "big"] +SizeUnit: TypeAlias = Literal[ + "b", + "kb", + "mb", + "gb", + "tb", + "bytes", + "kilobytes", + "megabytes", + "gigabytes", + "terabytes", +] +StartBy: TypeAlias = Literal[ + "window", + "datapoint", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", +] +SyncOnCloseMethod: TypeAlias = Literal["data", "all"] +TimeUnit: TypeAlias = Literal["ns", "us", "ms"] +UnicodeForm: TypeAlias = Literal["NFC", "NFKC", "NFD", "NFKD"] +UniqueKeepStrategy: TypeAlias = Literal["first", "last", "any", "none"] +UnstackDirection: TypeAlias = Literal["vertical", "horizontal"] +MapElementsStrategy: TypeAlias = Literal["thread_local", "threading"] + +# The following have a Rust enum equivalent with a different name +AsofJoinStrategy: TypeAlias = Literal["backward", "forward", "nearest"] # AsofStrategy +ClosedInterval: TypeAlias = Literal["left", "right", "both", "none"] # ClosedWindow +InterpolationMethod: TypeAlias = Literal["linear", "nearest"] +JoinStrategy: TypeAlias = Literal[ + "inner", "left", "right", "full", "semi", "anti", "cross" +] # JoinType +JoinWhereStrategy: TypeAlias = Literal["inner", "left", "right", "full"] # JoinType +ListToStructWidthStrategy: TypeAlias = Literal["first_non_null", "max_width"] + +# The following have no equivalent on the Rust side +ConcatMethod = Literal[ + "vertical", + "vertical_relaxed", + "diagonal", + "diagonal_relaxed", + "horizontal", + "horizontal_extend", + "align", + "align_full", + "align_inner", + "align_left", + "align_right", +] +CorrelationMethod: TypeAlias = Literal["pearson", "spearman"] +DbReadEngine: TypeAlias = Literal["adbc", "connectorx"] +DbWriteEngine: TypeAlias = Literal["sqlalchemy", "adbc"] +DbWriteMode: TypeAlias = Literal["replace", "append", "fail"] +EpochTimeUnit = Literal["ns", "us", "ms", "s", "d"] +JaxExportType: TypeAlias = Literal["array", "dict"] +Orientation: TypeAlias = Literal["col", "row"] +SearchSortedSide: TypeAlias = Literal["any", "left", "right"] +TorchExportType: TypeAlias = Literal["tensor", "dataset", "dict"] +TransferEncoding: TypeAlias = Literal["hex", "base64"] +WindowMappingStrategy: TypeAlias = Literal["group_to_rows", "join", "explode"] +ExplainFormat: TypeAlias = Literal["plain", "tree"] + +# type signature for allowed series init +ArrayLike: TypeAlias = Union[ + Iterable[Any], + "Series", + "PyArrowArray", + "PyArrowChunkedArray", + "NumpyArray", + "PandasSeries", + "PandasIndex", + "ArrowArrayExportable", + "ArrowStreamExportable", +] + + +# type signature for allowed frame init +FrameInitTypes: TypeAlias = Union[ + Mapping[str, ArrayLike | NonNestedLiteral | None], + Iterable[Any], + NumpyArray, + PyArrowTable, + PandasDataFrame, + "ArrowArrayExportable", + "ArrowStreamExportable", + TorchTensor, + "DataFrame", +] + +# Excel IO +ColumnFormatDict: TypeAlias = Mapping[ + # dict of colname(s) or selector(s) to format string or dict + ColumnNameOrSelector | tuple[ColumnNameOrSelector, ...], + Union[str, Mapping[str, str], "Format"], +] +ConditionalFormatDict: TypeAlias = Mapping[ + # dict of colname(s) to str, dict, or sequence of str/dict + ColumnNameOrSelector | Collection[str], + str | Mapping[str, Any] | Sequence[str | Mapping[str, Any]], +] +ColumnTotalsDefinition: TypeAlias = ( + Mapping[ColumnNameOrSelector | tuple[ColumnNameOrSelector], str] + | Sequence[str] + | bool +) +ColumnWidthsDefinition: TypeAlias = ( + Mapping[ColumnNameOrSelector, tuple[str, ...] | int] | int +) +RowTotalsDefinition: TypeAlias = ( + Mapping[str, str | Collection[str]] | Collection[str] | bool +) + +# standard/named hypothesis profiles used for parametric testing +ParametricProfileNames: TypeAlias = Literal["fast", "balanced", "expensive"] + +# typevars for core polars types +PolarsType = TypeVar("PolarsType", "DataFrame", "LazyFrame", "Series", "Expr") +FrameType = TypeVar("FrameType", "DataFrame", "LazyFrame") + +# type alias for supported spreadsheet engines +ExcelSpreadsheetEngine: TypeAlias = Literal["calamine", "openpyxl", "xlsx2csv"] + + +# minimal protocol definitions that can reasonably represent +# an executable connection, cursor, or equivalent object +class BasicConnection(Protocol): + def cursor(self, *args: Any, **kwargs: Any) -> Any: + """Return a cursor object.""" + + +class BasicCursor(Protocol): + def execute(self, *args: Any, **kwargs: Any) -> Any: + """Execute a query.""" + + +class Cursor(BasicCursor): + def fetchall(self, *args: Any, **kwargs: Any) -> Any: + """Fetch all results.""" + + def fetchmany(self, *args: Any, **kwargs: Any) -> Any: + """Fetch results in batches.""" + + +AlchemyConnection: TypeAlias = Union["Connection", "AlchemyEngine", "Session"] +AlchemyAsyncConnection: TypeAlias = Union[ + "AsyncConnection", "AsyncEngine", "AsyncSession", "async_sessionmaker[AsyncSession]" +] +ConnectionOrCursor: TypeAlias = ( + BasicConnection | BasicCursor | Cursor | AlchemyConnection | AlchemyAsyncConnection +) + +# Annotations for `__getitem__` methods +SingleIndexSelector: TypeAlias = int +MultiIndexSelector: TypeAlias = Union[ + slice, + range, + Sequence[int], + "Series", + "np.ndarray[Any, Any]", +] +SingleNameSelector: TypeAlias = str +MultiNameSelector: TypeAlias = Union[ + slice, + Sequence[str], + "Series", + "np.ndarray[Any, Any]", +] +BooleanMask: TypeAlias = Union[ + Sequence[bool], + "Series", + "np.ndarray[Any, Any]", +] +SingleColSelector: TypeAlias = SingleIndexSelector | SingleNameSelector +MultiColSelector: TypeAlias = MultiIndexSelector | MultiNameSelector | BooleanMask + +# LazyFrame engine selection +EngineTypeName: TypeAlias = Literal["auto", "in-memory", "streaming", "gpu"] +EngineType: TypeAlias = Union[EngineTypeName, "Engine"] + +PlanStage: TypeAlias = Literal["ir", "physical"] + +# Post-optimization callback receiving a Rust `NodeTraverser` and optional duration +# in nanoseconds. +PostOptCallback: TypeAlias = Callable[[Any, int | None], None] + + +# Result of an async collect, resolved either through asyncio or gevent. +AsyncResultT = TypeVar("AsyncResultT") +AsyncResult: TypeAlias = Union[ + "_GeventDataFrameResult[AsyncResultT]", "_AioDataFrameResult[AsyncResultT]" +] + +# Remote execution on Polars Cloud; mirrors `polars_cloud._typing` +ScalingMode: TypeAlias = Literal["auto", "single-node", "distributed"] +PlanTypePreference: TypeAlias = Literal["dot", "plain"] + +FileSource: TypeAlias = ( + str + | Path + | IO[bytes] + | bytes + | list[str] + | list[Path] + | list[IO[bytes]] + | list[bytes] +) + +JSONEncoder = Callable[[Any], bytes] | Callable[[Any], str] + +DeprecationType: TypeAlias = Literal[ + "function", + "renamed_parameter", + "nonkeyword_arguments", + "parameter_as_multi_positional", +] + + +__all__ = [ + "Alignment", + "Ambiguous", + "ApproxQuantileErrorBound", + "ApproxQuantileMethod", + "ArrowArrayExportable", + "ArrowStreamExportable", + "AsofJoinStrategy", + "AvroCompression", + "BooleanMask", + "CategoricalOrdering", + "ClosedInterval", + "ColumnFormatDict", + "ColumnNameOrSelector", + "ColumnTotalsDefinition", + "ColumnWidthsDefinition", + "ComparisonOperator", + "ConcatMethod", + "ConditionalFormatDict", + "ConnectionOrCursor", + "CorrelationMethod", + "CsvEncoding", + "CsvQuoteStyle", + "Cursor", + "DbReadEngine", + "DbWriteEngine", + "DbWriteMode", + "DeprecationType", + "Endianness", + "EngineType", + "EngineTypeName", + "EpochTimeUnit", + "ExcelSpreadsheetEngine", + "ExplainFormat", + "FileSource", + "FillNullStrategy", + "FloatFmt", + "FrameInitTypes", + "FrameType", + "IndexOrder", + "InterpolationMethod", + "IntoExpr", + "IntoExprColumn", + "IpcCompression", + "JSONEncoder", + "JaxExportType", + "JoinStrategy", + "JoinValidation", + "JoinWhereStrategy", + "Label", + "MaintainOrderJoin", + "MapElementsStrategy", + "MultiColSelector", + "MultiIndexSelector", + "MultiNameSelector", + "NdjsonCompression", + "NonExistent", + "NonNestedLiteral", + "NullBehavior", + "NumericLiteral", + "OneOrMoreDataTypes", + "Orientation", + "ParallelStrategy", + "ParametricProfileNames", + "ParquetCompression", + "PivotAgg", + "PlanTypePreference", + "PolarsDataType", + "PolarsIntegerType", + "PolarsTemporalType", + "PolarsType", + "PythonDataType", + "PythonLiteral", + "QuantileMethod", + "RankMethod", + "Roll", + "RowTotalsDefinition", + "ScalingMode", + "SchemaDefinition", + "SchemaDict", + "SearchSortedSide", + "SelectorType", + "SerializationFormat", + "SingleColSelector", + "SingleIndexSelector", + "SingleNameSelector", + "SizeUnit", + "StartBy", + "SyncOnCloseMethod", + "TemporalLiteral", + "TimeUnit", + "TorchExportType", + "TransferEncoding", + "UnicodeForm", + "UniqueKeepStrategy", + "UnstackDirection", + "WindowMappingStrategy", +] + + +class ParquetMetadataContext: + """ + The context given when writing file-level parquet metadata. + + .. warning:: + This functionality is considered **experimental**. It may be removed or + changed at any point without it being considered a breaking change. + """ + + def __init__(self, *, arrow_schema: str) -> None: + self.arrow_schema = arrow_schema + + arrow_schema: str #: The base64 encoded arrow schema that is going to be written into metadata. + + +ParquetMetadataFn: TypeAlias = Callable[[ParquetMetadataContext], dict[str, str]] +ParquetMetadata: TypeAlias = dict[str, str] | ParquetMetadataFn + +StorageOptionsDict: TypeAlias = dict[str, Any] diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__init__.py b/hug/lib/python3.12/site-packages/polars/_utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2f90a8fcd5bfeb61eac72a08991f5542df87089b --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/__init__.py @@ -0,0 +1,33 @@ +""" +Utility functions. + +Functions that are part of the public API are re-exported here. +""" + +from polars._utils.convert import ( + date_to_int, + datetime_to_int, + time_to_int, + timedelta_to_int, + to_py_date, + to_py_datetime, + to_py_decimal, + to_py_time, + to_py_timedelta, +) +from polars._utils.various import NO_DEFAULT, NoDefault, is_column + +__all__ = [ + "NoDefault", + "is_column", + "NO_DEFAULT", + "date_to_int", + "datetime_to_int", + "time_to_int", + "timedelta_to_int", + "to_py_date", + "to_py_datetime", + "to_py_decimal", + "to_py_time", + "to_py_timedelta", +] diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5dafb4ed116b91ec3636974d4d286b80009fb8d3 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/async_.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/async_.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de0537727ff0e7076e110402eb8e7abbcd89e446 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/async_.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/cache.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/cache.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89893baef2a1a3161572f0e0fd54ffe260de91ba Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/cache.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/cloud.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/cloud.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74c70c0c1bb74ca0505bb35bd0a879b586c66eb2 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/cloud.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/constants.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bdfd1fcb9e46bc3dea8270781bf0b1111608220 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/constants.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/convert.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/convert.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fb8960dee0b76cb079f8b597ce09837958e0834 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/convert.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/deprecation.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/deprecation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..134db1243689c7598a7c60816556809814d904da Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/deprecation.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/expired.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/expired.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2025730e7b7817301272265b72d155f647934a20 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/expired.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/getitem.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/getitem.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..538c54730a82c2aac6dfa7ee68cea26521793af9 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/getitem.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/logging.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/logging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cd21ed31dd24a37e2c9ff4f8eae8b6135093c5f Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/logging.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/monitoring.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/monitoring.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..692ae3b2fb84dd972ae897df653fa916bda41e13 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/monitoring.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/nest_asyncio.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/nest_asyncio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d802a57ee911f62d6595c96bf07e1bb91bdc734 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/nest_asyncio.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/parquet.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/parquet.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6ece87645938611c7a4c4538a0925356669a566 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/parquet.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/polars_version.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/polars_version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56c152e0cd1ec4e8abde0d568b3fc9e2402eb287 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/polars_version.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/pycapsule.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/pycapsule.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b4508263f9d65606037a8a70ce290afea34e995 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/pycapsule.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/reduce_balanced.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/reduce_balanced.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e40843ab19a2c522957da678926f2619bc573364 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/reduce_balanced.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/serde.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/serde.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e24b0f09b24251100a476f04e338d4986ec1f93 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/serde.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/slice.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/slice.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..587a804a8c8abafc12d9194122dd6599700f5ac2 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/slice.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/threading.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/threading.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0361bc6772ebb9a20d71b6e935f69114de7902e Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/threading.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/udfs.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/udfs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60d60887b18277042f4cfd6f36066c0d386255ec Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/udfs.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/unstable.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/unstable.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3aded5e64626000f4b80e21bb70e500bbb88e5a7 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/unstable.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/various.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/various.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eecd23de013f2f12476d7bbd557f5aed7ab4eecd Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/various.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/wrap.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/wrap.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8108d0dc70ba58b08e94f747de87f4493060cfbd Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/__pycache__/wrap.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/async_.py b/hug/lib/python3.12/site-packages/polars/_utils/async_.py new file mode 100644 index 0000000000000000000000000000000000000000..59e8f32e5c6463c28077b3d26facc3880fad471e --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/async_.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +from collections.abc import Awaitable +from typing import TYPE_CHECKING, Any, Generic, TypeVar + +from polars._dependencies import _GEVENT_AVAILABLE +from polars._utils.wrap import wrap_df + +if TYPE_CHECKING: + from asyncio.futures import Future + from collections.abc import Generator + + from polars import DataFrame + from polars._plr import PyDataFrame + + +T = TypeVar("T") + + +class _GeventDataFrameResult(Generic[T]): + __slots__ = ("_result", "_value", "_watcher") + + def __init__(self) -> None: + if not _GEVENT_AVAILABLE: + msg = ( + "gevent is required for using LazyFrame.collect_async(gevent=True) or" + "polars.collect_all_async(gevent=True)" + ) + raise ImportError(msg) + + from gevent.event import AsyncResult # type: ignore[import-untyped] + from gevent.hub import get_hub # type: ignore[import-untyped] + + self._value: None | Exception | PyDataFrame | list[PyDataFrame] = None + self._result = AsyncResult() + + self._watcher = get_hub().loop.async_() + self._watcher.start(self._watcher_callback) + + def get( + self, + block: bool = True, # noqa: FBT001 + timeout: float | int | None = None, + ) -> T: + return self.result.get(block=block, timeout=timeout) + + @property + def result(self) -> Any: + # required if we did not made any switches and just want results later + # with block=False and possibly without timeout + if self._value is not None and not self._result.ready(): + self._watcher_callback() + return self._result + + def _watcher_callback(self) -> None: + if isinstance(self._value, Exception): + self._result.set_exception(self._value) + else: + self._result.set(self._value) + self._watcher.close() + + def _callback(self, obj: PyDataFrame | Exception) -> None: + if not isinstance(obj, Exception): + obj = wrap_df(obj) # type: ignore[assignment] + self._value = obj + self._watcher.send() + + def _callback_all(self, obj: list[PyDataFrame] | Exception) -> None: + if not isinstance(obj, Exception): + obj = [wrap_df(pydf) for pydf in obj] # type: ignore[misc] + self._value = obj + self._watcher.send() + + +class _AioDataFrameResult(Awaitable[T], Generic[T]): + __slots__ = ("loop", "result") + + def __init__(self) -> None: + from asyncio import get_event_loop + + self.loop = get_event_loop() + self.result: Future[T] = self.loop.create_future() + + def __await__(self) -> Generator[Any, None, T]: + return self.result.__await__() + + def _callback( + self: _AioDataFrameResult[DataFrame], obj: PyDataFrame | Exception + ) -> None: + if isinstance(obj, Exception): + self.loop.call_soon_threadsafe(self.result.set_exception, obj) + else: + self.loop.call_soon_threadsafe( + self.result.set_result, + wrap_df(obj), + ) + + def _callback_all( + self: _AioDataFrameResult[list[DataFrame]], obj: list[PyDataFrame] | Exception + ) -> None: + if isinstance(obj, Exception): + self.loop.call_soon_threadsafe(self.result.set_exception, obj) + else: + self.loop.call_soon_threadsafe( + self.result.set_result, + [wrap_df(pydf) for pydf in obj], + ) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/cache.py b/hug/lib/python3.12/site-packages/polars/_utils/cache.py new file mode 100644 index 0000000000000000000000000000000000000000..2342f96148bb955120e0fe1c3ae33f3a31fd41c3 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/cache.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +from collections import OrderedDict +from collections.abc import MutableMapping +from typing import TYPE_CHECKING, Any, TypeVar, overload + +from polars._utils.various import NO_DEFAULT + +if TYPE_CHECKING: + from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView + from typing import Self + + from polars._utils.various import NoDefault + +D = TypeVar("D") +K = TypeVar("K") +V = TypeVar("V") + + +class LRUCache(MutableMapping[K, V]): + def __init__(self, maxsize: int) -> None: + """ + Initialize an LRU (Least Recently Used) cache with a specified maximum size. + + Parameters + ---------- + maxsize : int + The maximum number of items the cache can hold. + + Examples + -------- + >>> from polars._utils.cache import LRUCache + >>> cache = LRUCache[str, int](maxsize=3) + >>> cache["a"] = 1 + >>> cache["b"] = 2 + >>> cache["c"] = 3 + >>> cache["d"] = 4 # evicts the least recently used item ("a"), as maxsize=3 + >>> print(cache["b"]) # accessing "b" marks it as recently used + 2 + >>> print(list(cache.keys())) # show the current keys in LRU order + ['c', 'd', 'b'] + >>> cache.get("xyz", "not found") + 'not found' + """ + self._items: OrderedDict[K, V] = OrderedDict() + self.maxsize = maxsize + + def __bool__(self) -> bool: + """Returns True if the cache is not empty, False otherwise.""" + return bool(self._items) + + def __contains__(self, key: Any) -> bool: + """Check if the key is in the cache.""" + return key in self._items + + def __delitem__(self, key: K) -> None: + """Remove the item with the specified key from the cache.""" + if key not in self._items: + msg = f"{key!r} not found in cache" + raise KeyError(msg) + del self._items[key] + + def __getitem__(self, key: K) -> V: + """Raises KeyError if the key is not found.""" + if key not in self._items: + msg = f"{key!r} not found in cache" + raise KeyError(msg) + + # moving accessed items to the end marks them as recently used + self._items.move_to_end(key) + return self._items[key] + + def __iter__(self) -> Iterator[K]: + """Iterate over the keys in the cache.""" + yield from self._items + + def __len__(self) -> int: + """Number of items in the cache.""" + return len(self._items) + + def __setitem__(self, key: K, value: V) -> None: + """Insert a value into the cache.""" + if self._max_size == 0: + return + if key in self: + # moving accessed items to the end marks them as recently used + self._items.move_to_end(key) + else: + while len(self) >= self._max_size: + self.popitem() + self._items[key] = value + + def __repr__(self) -> str: + """Return a string representation of the cache.""" + all_items = list(self._items.items()) + if len(self) > 4: + items = ( + ", ".join(f"{k!r}: {v!r}" for k, v in all_items[:2]) + + " ..., " + + ", ".join(f"{k!r}: {v!r}" for k, v in all_items[-2:]) + ) + else: + items = ", ".join(f"{k!r}: {v!r}" for k, v in all_items) + return f"{self.__class__.__name__}({{{items}}}, maxsize={self._max_size}, currsize={len(self)})" + + def clear(self) -> None: + """Clear the cache, removing all items.""" + self._items.clear() + + @overload + def get(self, key: K, default: None = None) -> V | None: ... + + @overload + def get(self, key: K, default: D = ...) -> V | D: ... + + def get(self, key: K, default: D | V | None = None) -> V | D | None: + """Return value associated with `key` if present, otherwise return `default`.""" + if key in self: + # moving accessed items to the end marks them as recently used + self._items.move_to_end(key) + return self._items[key] + return default + + @classmethod + def fromkeys(cls, maxsize: int, *, keys: Iterable[K], value: V) -> Self: + """Initialize cache with keys from an iterable, all set to the same value.""" + cache = cls(maxsize) + for key in keys: + cache[key] = value + return cache + + def items(self) -> ItemsView[K, V]: + """Return an iterable view of the cache's items (keys and values).""" + return self._items.items() + + def keys(self) -> KeysView[K]: + """Return an iterable view of the cache's keys.""" + return self._items.keys() + + @property + def maxsize(self) -> int: + return self._max_size + + @maxsize.setter + def maxsize(self, n: int) -> None: + """Set new maximum cache size; cache is trimmed if value is smaller.""" + if n < 0: + msg = f"`maxsize` cannot be negative; found {n}" + raise ValueError(msg) + while len(self) > n: + self.popitem() + self._max_size = n + + def pop(self, key: K, default: D | NoDefault = NO_DEFAULT) -> V | D: + """ + Remove specified key from the cache and return the associated value. + + If the key is not found, `default` is returned (if given). + Otherwise, a KeyError is raised. + """ + if (item := self._items.pop(key, default)) is NO_DEFAULT: + msg = f"{key!r} not found in cache" + raise KeyError(msg) + return item + + def popitem(self) -> tuple[K, V]: + """Remove the least recently used value; raises KeyError if cache is empty.""" + return self._items.popitem(last=False) + + def values(self) -> ValuesView[V]: + """Return an iterable view of the cache's values.""" + return self._items.values() diff --git a/hug/lib/python3.12/site-packages/polars/_utils/cloud.py b/hug/lib/python3.12/site-packages/polars/_utils/cloud.py new file mode 100644 index 0000000000000000000000000000000000000000..16ca34d586590933f1a9b63b201b6d992e4be771 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/cloud.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import polars._plr as plr +from polars.lazyframe.opt_flags import DEFAULT_QUERY_OPT_FLAGS + +if TYPE_CHECKING: + from polars import LazyFrame, QueryOptFlags + + +def prepare_cloud_plan( + lf: LazyFrame, + *, + optimizations: QueryOptFlags = DEFAULT_QUERY_OPT_FLAGS, +) -> bytes: + """ + Prepare the given LazyFrame for execution on Polars Cloud. + + Parameters + ---------- + lf + The LazyFrame to prepare. + optimizations + Optimizations to enable or disable in the query optimizer. + + Raises + ------ + InvalidOperationError + If the given LazyFrame is not eligible to be run on Polars Cloud. + The following conditions will disqualify a LazyFrame from being eligible: + + - Contains a user-defined function + - Scans or sinks to a local filesystem + ComputeError + If the given LazyFrame cannot be serialized. + """ + optimizations = optimizations.__copy__() + pylf = lf._ldf.with_optimizations(optimizations._pyoptflags) + return plr.prepare_cloud_plan(pylf) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/constants.py b/hug/lib/python3.12/site-packages/polars/_utils/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..47b0e5c1e713d4a55ab552d73ded105c2d8170c2 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/constants.py @@ -0,0 +1,30 @@ +from datetime import UTC, date, datetime +from typing import Final + +# Integer ranges +I8_MIN: Final = -(2**7) +I16_MIN: Final = -(2**15) +I32_MIN: Final = -(2**31) +I64_MIN: Final = -(2**63) +I128_MIN: Final = -(2**127) +I8_MAX: Final = 2**7 - 1 +I16_MAX: Final = 2**15 - 1 +I32_MAX: Final = 2**31 - 1 +I64_MAX: Final = 2**63 - 1 +I128_MAX: Final = 2**127 - 1 +U8_MAX: Final = 2**8 - 1 +U16_MAX: Final = 2**16 - 1 +U32_MAX: Final = 2**32 - 1 +U64_MAX: Final = 2**64 - 1 +U128_MAX: Final = 2**128 - 1 + +# Temporal +SECONDS_PER_DAY: Final = 86_400 +SECONDS_PER_HOUR: Final = 3_600 +NS_PER_SECOND: Final = 1_000_000_000 +US_PER_SECOND: Final = 1_000_000 +MS_PER_SECOND: Final = 1_000 + +EPOCH_DATE: Final = date(1970, 1, 1) +EPOCH: Final = datetime(1970, 1, 1).replace(tzinfo=None) +EPOCH_UTC: Final = datetime(1970, 1, 1, tzinfo=UTC) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/__init__.py b/hug/lib/python3.12/site-packages/polars/_utils/construction/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1b9a543bfb6d127dad19bf698c923272f5f76843 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/construction/__init__.py @@ -0,0 +1,46 @@ +from polars._utils.construction.dataframe import ( + arrow_to_pydf, + dataframe_to_pydf, + dict_to_pydf, + iterable_to_pydf, + numpy_to_pydf, + pandas_to_pydf, + sequence_to_pydf, + series_to_pydf, +) +from polars._utils.construction.other import ( + coerce_arrow, + pandas_series_to_arrow, +) +from polars._utils.construction.series import ( + arrow_to_pyseries, + dataframe_to_pyseries, + iterable_to_pyseries, + numpy_to_pyseries, + pandas_to_pyseries, + sequence_to_pyseries, + series_to_pyseries, +) + +__all__ = [ + # dataframe + "arrow_to_pydf", + "dataframe_to_pydf", + "dict_to_pydf", + "iterable_to_pydf", + "numpy_to_pydf", + "pandas_to_pydf", + "sequence_to_pydf", + "series_to_pydf", + # series + "arrow_to_pyseries", + "dataframe_to_pyseries", + "iterable_to_pyseries", + "numpy_to_pyseries", + "pandas_to_pyseries", + "sequence_to_pyseries", + "series_to_pyseries", + # other + "coerce_arrow", + "pandas_series_to_arrow", +] diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f3897529200480a7b324e18a86ecb7e8ae83f78 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/dataframe.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/dataframe.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6993c518fe90f53f1f13911dc1f8a2272baead3 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/dataframe.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/other.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/other.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..150c608aa1b9dd7364198b60ffdc57e8a1f3cd47 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/other.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/series.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/series.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42ee25c2ed9c54249bce357567a94c3056386a0d Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/series.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/utils.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6a6a0e827982629be41aeff7aaaa96ebdc1d229 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/construction/__pycache__/utils.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/dataframe.py b/hug/lib/python3.12/site-packages/polars/_utils/construction/dataframe.py new file mode 100644 index 0000000000000000000000000000000000000000..09c65aefc8965aaf81785b4328e2d715e4f5356e --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/construction/dataframe.py @@ -0,0 +1,1444 @@ +from __future__ import annotations + +import contextlib +from collections.abc import Generator, Mapping, Sequence +from datetime import date, datetime, time, timedelta +from functools import singledispatch +from itertools import islice, zip_longest +from operator import attrgetter, itemgetter +from typing import ( + TYPE_CHECKING, + Any, + cast, +) + +import polars._reexport as pl +import polars._utils.construction as plc +from polars import functions as F +from polars._dependencies import ( + _NUMPY_AVAILABLE, + _PYARROW_AVAILABLE, + _check_for_numpy, + _check_for_pandas, +) +from polars._dependencies import numpy as np +from polars._dependencies import pandas as pd +from polars._dependencies import pyarrow as pa +from polars._utils.construction.utils import ( + contains_nested, + get_first_non_none, + is_dataclass_instance, + is_namedtuple, + is_pydantic_model, + is_simple_numpy_backed_pandas_series, + is_sqlalchemy_row, + nt_unpack, + try_get_type_hints, +) +from polars._utils.various import ( + _is_generator, + arrlen, + parse_version, +) +from polars._warnings import issue_warning +from polars.datatypes import ( + N_INFER_DEFAULT, + Categorical, + Duration, + Enum, + Object, + String, + Struct, + Unknown, + is_polars_dtype, + parse_into_dtype, + try_parse_into_dtype, +) +from polars.exceptions import DataOrientationWarning, ShapeError +from polars.meta import thread_pool_size + +with contextlib.suppress(ImportError): # Module not available when building docs + from polars._plr import PyDataFrame + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable, MutableMapping + + from polars import DataFrame, Series + from polars._plr import PySeries + from polars._typing import ( + ArrayLike, + NonNestedLiteral, + Orientation, + PolarsDataType, + SchemaDefinition, + SchemaDict, + ) + +_MIN_NUMPY_SIZE_FOR_MULTITHREADING = 1000 + + +def dict_to_pydf( + data: Mapping[str, ArrayLike | NonNestedLiteral | None], + schema: SchemaDefinition | None = None, + *, + schema_overrides: SchemaDict | None = None, + strict: bool = True, + nan_to_null: bool = False, + allow_multithreaded: bool = True, +) -> PyDataFrame: + """Construct a PyDataFrame from a dictionary of sequences.""" + if isinstance(schema, Mapping) and data: + if not all((col in schema) for col in data): + msg = "the given column-schema names do not match the data dictionary" + raise ValueError(msg) + data = {col: data[col] for col in schema} + + column_names, schema_overrides = _unpack_schema( + schema, lookup_names=data.keys(), schema_overrides=schema_overrides + ) + if not column_names: + column_names = list(data) + + if data and _NUMPY_AVAILABLE: + # if there are 3 or more numpy arrays of sufficient size, we multi-thread: + count_numpy = sum( + int( + allow_multithreaded + and _check_for_numpy(val) + and isinstance(val, np.ndarray) + and len(val) > _MIN_NUMPY_SIZE_FOR_MULTITHREADING + # integers and non-nan floats are zero-copy + and nan_to_null + and val.dtype in (np.float32, np.float64) + ) + for val in data.values() + ) + if count_numpy >= 3: + # yes, multi-threading was easier in python here; we cannot have multiple + # threads running python and release the gil in pyo3 (it will deadlock). + from concurrent.futures import ThreadPoolExecutor + + pool_size = thread_pool_size() + with ThreadPoolExecutor(max_workers=pool_size) as pool: + data = dict( + zip( + column_names, + pool.map( + lambda t: ( + pl.Series(t[0], t[1], nan_to_null=nan_to_null) + if isinstance(t[1], np.ndarray) + else t[1] + ), + list(data.items()), + ), + strict=True, + ) + ) + + if not data and schema_overrides: + data_series = [ + pl.Series( + name, + [], + dtype=schema_overrides.get(name), + strict=strict, + nan_to_null=nan_to_null, + )._s + for name in column_names + ] + else: + data_series = [ + s._s + for s in _expand_dict_values( + data, + schema_overrides=schema_overrides, + strict=strict, + nan_to_null=nan_to_null, + ).values() + ] + + data_series = _handle_columns_arg(data_series, columns=column_names, from_dict=True) + pydf = PyDataFrame(data_series) + + if schema_overrides and pydf.dtypes() != list(schema_overrides.values()): + pydf = _post_apply_columns( + pydf, column_names, schema_overrides=schema_overrides, strict=strict + ) + return pydf + + +def _unpack_schema( + schema: SchemaDefinition | None, + *, + schema_overrides: SchemaDict | None = None, + n_expected: int | None = None, + lookup_names: Iterable[str] | None = None, +) -> tuple[list[str], SchemaDict]: + """ + Unpack column names and create dtype lookup. + + Works for any (name, dtype) pairs or schema dict input, + overriding any inferred dtypes with explicit dtypes if supplied. + """ + + def _normalize_dtype(dtype: Any) -> PolarsDataType: + """Parse non-Polars data types as Polars data types.""" + if is_polars_dtype(dtype, include_unknown=True): + return dtype + else: + return parse_into_dtype(dtype) + + def _parse_schema_overrides( + schema_overrides: SchemaDict | None = None, + ) -> dict[str, PolarsDataType]: + """Parse schema overrides as a dictionary of name to Polars data type.""" + if schema_overrides is None: + return {} + + return { + name: _normalize_dtype(dtype) for name, dtype in schema_overrides.items() + } + + schema_overrides = _parse_schema_overrides(schema_overrides) + + # fast path for empty schema + if not schema: + columns = ( + [f"column_{i}" for i in range(n_expected)] if n_expected is not None else [] + ) + return columns, schema_overrides + + # determine column names from schema + if isinstance(schema, Mapping): + column_names: list[str] = list(schema) + schema = list(schema.items()) + else: + column_names = [] + for i, col in enumerate(schema): + if isinstance(col, str): + unnamed = not col and col not in schema_overrides + col = f"column_{i}" if unnamed else col + else: + col = col[0] + column_names.append(col) + + if n_expected is not None and len(column_names) != n_expected: + msg = "data does not match the number of columns" + raise ShapeError(msg) + + # determine column dtypes from schema and lookup_names + lookup: dict[str, str] | None = ( + { + col: name + for col, name in zip_longest(column_names, lookup_names) + if name is not None + } + if lookup_names + else None + ) + + column_dtypes: dict[str, PolarsDataType] = {} + for col in schema: + if isinstance(col, str): + continue + + name, dtype = col + if dtype is None: + continue + else: + dtype = _normalize_dtype(dtype) + name = lookup.get(name, name) if lookup else name + column_dtypes[name] = dtype # type: ignore[assignment] + + # apply schema overrides + if schema_overrides: + column_dtypes.update(schema_overrides) + + return column_names, column_dtypes + + +def _handle_columns_arg( + data: list[PySeries], + columns: Sequence[str] | None = None, + *, + from_dict: bool = False, +) -> list[PySeries]: + """Rename data according to columns argument.""" + if columns is None: + return data + elif not data: + return [pl.Series(name=c)._s for c in columns] + elif len(data) != len(columns): + msg = f"dimensions of columns arg ({len(columns)}) must match data dimensions ({len(data)})" + raise ValueError(msg) + + if from_dict: + series_map = {s.name(): s for s in data} + if all((col in series_map) for col in columns): + return [series_map[col] for col in columns] + + for i, c in enumerate(columns): + if c != data[i].name(): + data[i] = data[i].clone() + data[i].rename(c) + + return data + + +def _post_apply_columns( + pydf: PyDataFrame, + columns: SchemaDefinition | None, + structs: dict[str, Struct] | None = None, + schema_overrides: SchemaDict | None = None, + *, + strict: bool = True, +) -> PyDataFrame: + """Apply 'columns' param *after* PyDataFrame creation (if no alternative).""" + pydf_columns, pydf_dtypes = pydf.columns(), pydf.dtypes() + columns, dtypes = _unpack_schema( + (columns or pydf_columns), schema_overrides=schema_overrides + ) + column_subset: list[str] = [] + if columns != pydf_columns: + if len(columns) < len(pydf_columns) and columns == pydf_columns[: len(columns)]: + column_subset = columns + else: + pydf.set_column_names(columns) + + column_casts = [] + for i, col in enumerate(columns): + dtype = dtypes.get(col) + pydf_dtype = pydf_dtypes[i] + if dtype is None: + continue + if dtype == Categorical != pydf_dtype: + column_casts.append(F.col(col).cast(Categorical, strict=strict)._pyexpr) + elif dtype == Enum != pydf_dtype: + column_casts.append(F.col(col).cast(dtype, strict=strict)._pyexpr) + elif structs and (struct := structs.get(col)) and struct != pydf_dtype: + column_casts.append(F.col(col).cast(struct, strict=strict)._pyexpr) + elif dtype != Unknown and dtype != pydf_dtype: + if dtype.is_temporal() and dtype != Duration and pydf_dtype == String: + temporal_cast = F.col(col).str.strptime(dtype, strict=strict)._pyexpr # type: ignore[arg-type] + column_casts.append(temporal_cast) + else: + column_casts.append(F.col(col).cast(dtype, strict=strict)._pyexpr) + + if column_casts or column_subset: + pyldf = pydf.lazy() + if column_casts: + pyldf = pyldf.with_columns(column_casts) + if column_subset: + pyldf = pyldf.select([F.col(col)._pyexpr for col in column_subset]) + pydf = pyldf.collect(engine="in-memory", lambda_post_opt=None) + + return pydf + + +def _expand_dict_values( + data: Mapping[str, ArrayLike | NonNestedLiteral | None], + *, + schema_overrides: SchemaDict | None = None, + strict: bool = True, + order: Sequence[str] | None = None, + nan_to_null: bool = False, +) -> dict[str, Series]: + """Expand any scalar values in dict data (propagate literal as array).""" + updated_data = {} + if data: + if any(isinstance(val, pl.Expr) for val in data.values()): + msg = ( + "passing Expr objects to the DataFrame constructor is not supported" + "\n\nHint: Try evaluating the expression first using `select`," + " or if you meant to create an Object column containing expressions," + " pass a list of Expr objects instead." + ) + raise TypeError(msg) + + dtypes = schema_overrides or {} + data = _expand_dict_data(data, dtypes, strict=strict) + array_len = max((arrlen(val) or 0) for val in data.values()) + if array_len > 0: + for name, val in data.items(): + dtype = dtypes.get(name) + if isinstance(val, dict) and dtype != Struct: + vdf = pl.DataFrame(val, strict=strict) + if ( + vdf.height == 1 + and array_len > 1 + and all(not d.is_nested() for d in vdf.schema.values()) + ): + s_vals = { + nm: vdf[nm].extend_constant(v, n=(array_len - 1)) + for nm, v in val.items() + } + st = pl.DataFrame(s_vals).to_struct(name) + else: + st = vdf.to_struct(name) + updated_data[name] = st + + elif isinstance(val, pl.Series): + s = val.rename(name) if name != val.name else val + if dtype and dtype != s.dtype: + s = s.cast(dtype, strict=strict) + updated_data[name] = s + + elif arrlen(val) is not None or _is_generator(val): + val = cast("Iterable[Any]", val) # help type-checkers + updated_data[name] = pl.Series( + name=name, + values=val, + dtype=dtype, + strict=strict, + nan_to_null=nan_to_null, + ) + elif val is None or isinstance( + val, (int, float, str, bytes, bool, date, datetime, time, timedelta) + ): + updated_data[name] = F.repeat( + val, array_len, dtype=dtype, eager=True + ).alias(name) + else: + updated_data[name] = pl.Series( + name=name, values=[val] * array_len, dtype=dtype, strict=strict + ) + + elif all((arrlen(val) == 0) for val in data.values()): + for name, val in data.items(): + val = cast("Iterable[Any]", val) # help type-checkers + updated_data[name] = pl.Series( + name, + values=val, + dtype=dtypes.get(name), + strict=strict, + ) + + elif all((arrlen(val) is None) for val in data.values()): + for name, val in data.items(): + updated_data[name] = pl.Series( + name, + values=(val if _is_generator(val) else [val]), + dtype=dtypes.get(name), + strict=strict, + ) + if order and list(updated_data) != order: + return {col: updated_data.pop(col) for col in order} + return updated_data + + +def _expand_dict_data( + data: Mapping[str, ArrayLike | NonNestedLiteral | None], + dtypes: SchemaDict, + *, + strict: bool = True, +) -> Mapping[str, ArrayLike | NonNestedLiteral | None]: + """ + Expand any unsized generators/iterators. + + (Note that `range` is sized, and will take a fast-path on Series init). + """ + expanded_data: dict[str, ArrayLike | NonNestedLiteral | None] = {} + for name, val in data.items(): + expanded_data[name] = ( + pl.Series(name, val, dtypes.get(name), strict=strict) + if _is_generator(val) + else val + ) + return expanded_data + + +_resolved_sequence_handlers: dict[type, Callable[..., PyDataFrame]] = {} + + +def sequence_to_pydf( + data: Sequence[Any], + schema: SchemaDefinition | None = None, + *, + schema_overrides: SchemaDict | None = None, + strict: bool = True, + orient: Orientation | None = None, + infer_schema_length: int | None = N_INFER_DEFAULT, + nan_to_null: bool = False, +) -> PyDataFrame: + """Construct a PyDataFrame from a sequence.""" + if not data: + return dict_to_pydf({}, schema=schema, schema_overrides=schema_overrides) + + first_element = get_first_non_none(data) + to_pydf = _resolved_sequence_handlers.get( + type(first_element), _sequence_to_pydf_dispatcher + ) + return to_pydf( + first_element, + data=data, + schema=schema, + schema_overrides=schema_overrides, + strict=strict, + orient=orient, + infer_schema_length=infer_schema_length, + nan_to_null=nan_to_null, + ) + + +@singledispatch +def _sequence_to_pydf_dispatcher( + first_element: Any, + data: Sequence[Any], + schema: SchemaDefinition | None, + *, + schema_overrides: SchemaDict | None, + strict: bool = True, + orient: Orientation | None, + infer_schema_length: int | None, + nan_to_null: bool = False, +) -> PyDataFrame: + # note: ONLY python-native data should participate in singledispatch registration + # via top-level decorators, otherwise we have to import the associated module. + # third-party libraries (such as numpy/pandas) should be identified inline (below) + # and THEN memoized (here) so as not to break lazy-loading behaviour. + + to_pydf: Callable[..., PyDataFrame] + memo_key = type(first_element) + memoize = True + + if isinstance(first_element, Generator): + to_pydf = _sequence_of_sequence_to_pydf + data = [list(row) for row in data] + first_element = data[0] + # note: rows were materialised, so resolution + # belongs to the call rather than to the type + memoize = False + + elif isinstance(first_element, pl.Series): + to_pydf = _sequence_of_series_to_pydf + + elif _check_for_numpy(first_element) and isinstance(first_element, np.ndarray): + to_pydf = _sequence_of_numpy_to_pydf + + elif _check_for_pandas(first_element) and isinstance( + first_element, (pd.Series, pd.Index, pd.DatetimeIndex) + ): + to_pydf = _sequence_of_pandas_to_pydf + + elif is_dataclass_instance(first_element): + to_pydf = _sequence_of_dataclasses_to_pydf + + elif is_pydantic_model(first_element): + to_pydf = _sequence_of_pydantic_models_to_pydf + + elif is_sqlalchemy_row(first_element): + to_pydf = _sequence_of_tuple_to_pydf + + elif isinstance(first_element, Sequence) and not isinstance(first_element, str): + to_pydf = _sequence_of_sequence_to_pydf + else: + to_pydf = _sequence_of_elements_to_pydf + + if memoize: + _resolved_sequence_handlers[memo_key] = to_pydf + + return to_pydf( + first_element, + data=data, + schema=schema, + schema_overrides=schema_overrides, + strict=strict, + orient=orient, + infer_schema_length=infer_schema_length, + nan_to_null=nan_to_null, + ) + + +@_sequence_to_pydf_dispatcher.register(list) +def _sequence_of_sequence_to_pydf( + first_element: Sequence[Any] | np.ndarray[Any, Any], + data: Sequence[Any], + schema: SchemaDefinition | None, + *, + schema_overrides: SchemaDict | None, + strict: bool, + orient: Orientation | None, + infer_schema_length: int | None, + nan_to_null: bool = False, +) -> PyDataFrame: + if orient is None: + if schema is None: + orient = "col" + else: + # Try to infer orientation from schema length and data dimensions + is_row_oriented = (len(schema) == len(first_element)) and ( + len(schema) != len(data) + ) + orient = "row" if is_row_oriented else "col" + + if is_row_oriented: + issue_warning( + "Row orientation inferred during DataFrame construction." + ' Explicitly specify the orientation by passing `orient="row"` to silence this warning.', + DataOrientationWarning, + ) + + if orient == "row": + column_names, schema_overrides = _unpack_schema( + schema, schema_overrides=schema_overrides, n_expected=len(first_element) + ) + local_schema_override = ( + _include_unknowns(schema_overrides, column_names) + if schema_overrides + else {} + ) + + unpack_nested = False + for col, tp in local_schema_override.items(): + if tp in (Categorical, Enum): + local_schema_override[col] = String + elif not unpack_nested and (tp.base_type() in (Unknown, Struct)): + unpack_nested = contains_nested( + getattr(first_element, col, None).__class__, is_namedtuple + ) + + if unpack_nested: + dicts = [nt_unpack(d) for d in data] + pydf = PyDataFrame.from_dicts( + dicts, + schema=None, + schema_overrides=None, + strict=strict, + infer_schema_length=infer_schema_length, + ) + else: + pydf = PyDataFrame.from_rows( + data, + schema=local_schema_override or None, + infer_schema_length=infer_schema_length, + ) + if column_names or schema_overrides: + pydf = _post_apply_columns( + pydf, column_names, schema_overrides=schema_overrides, strict=strict + ) + return pydf + + elif orient == "col": + column_names, schema_overrides = _unpack_schema( + schema, schema_overrides=schema_overrides, n_expected=len(data) + ) + data_series: list[PySeries] = [ + pl.Series( + column_names[i], + element, + dtype=schema_overrides.get(column_names[i]), + strict=strict, + nan_to_null=nan_to_null, + )._s + for i, element in enumerate(data) + ] + return PyDataFrame(data_series) + + else: + msg = f"`orient` must be one of {{'col', 'row', None}}, got {orient!r}" + raise ValueError(msg) + + +def _sequence_of_series_to_pydf( + first_element: Series, # noqa: ARG001 + data: Sequence[Any], + schema: SchemaDefinition | None, + *, + schema_overrides: SchemaDict | None, + strict: bool, + **kwargs: Any, # noqa: ARG001 +) -> PyDataFrame: + series_names = [s.name for s in data] + column_names, schema_overrides = _unpack_schema( + schema or series_names, + schema_overrides=schema_overrides, + n_expected=len(data), + ) + data_series: list[PySeries] = [] + for i, s in enumerate(data): + if not s.name: + s = s.alias(column_names[i]) + new_dtype = schema_overrides.get(column_names[i]) + if new_dtype and new_dtype != s.dtype: + s = s.cast(new_dtype, strict=strict, wrap_numerical=False) + data_series.append(s._s) + + data_series = _handle_columns_arg(data_series, columns=column_names) + return PyDataFrame(data_series) + + +@_sequence_to_pydf_dispatcher.register(tuple) +def _sequence_of_tuple_to_pydf( + first_element: tuple[Any, ...], + data: Sequence[Any], + schema: SchemaDefinition | None, + *, + schema_overrides: SchemaDict | None, + strict: bool, + orient: Orientation | None, + infer_schema_length: int | None, + nan_to_null: bool = False, +) -> PyDataFrame: + # infer additional meta information if namedtuple + if is_namedtuple(first_element.__class__) or is_sqlalchemy_row(first_element): + if schema is None: + schema = first_element._fields # type: ignore[attr-defined] + annotations = getattr(first_element, "__annotations__", None) + if annotations and len(annotations) == len(schema): + schema = [ + (name, try_parse_into_dtype(tp)) + for name, tp in first_element.__annotations__.items() + ] + if orient is None: + orient = "row" + + # ...then defer to generic sequence processing + return _sequence_of_sequence_to_pydf( + first_element, + data=data, + schema=schema, + schema_overrides=schema_overrides, + strict=strict, + orient=orient, + infer_schema_length=infer_schema_length, + nan_to_null=nan_to_null, + ) + + +@_sequence_to_pydf_dispatcher.register(Mapping) +@_sequence_to_pydf_dispatcher.register(dict) +def _sequence_of_dict_to_pydf( + first_element: Mapping[str, Any], # noqa: ARG001 + data: Sequence[Any], + schema: SchemaDefinition | None, + *, + schema_overrides: SchemaDict | None, + strict: bool, + infer_schema_length: int | None, + **kwargs: Any, # noqa: ARG001 +) -> PyDataFrame: + column_names, schema_overrides = _unpack_schema( + schema, schema_overrides=schema_overrides + ) + dicts_schema = ( + _include_unknowns(schema_overrides, column_names or list(schema_overrides)) + if column_names + else None + ) + + pydf = PyDataFrame.from_dicts( + data, + dicts_schema, + schema_overrides, + strict=strict, + infer_schema_length=infer_schema_length, + ) + return pydf + + +@_sequence_to_pydf_dispatcher.register(str) +def _sequence_of_elements_to_pydf( + first_element: Any, # noqa: ARG001 + data: Sequence[Any], + schema: SchemaDefinition | None, + schema_overrides: SchemaDict | None, + *, + strict: bool, + orient: Orientation | None, + **kwargs: Any, +) -> PyDataFrame: + if orient == "row": + return _sequence_of_sequence_to_pydf( + first_element=data, + data=[data], + schema=schema, + schema_overrides=schema_overrides, + strict=strict, + orient=orient, + **kwargs, + ) + + column_names, schema_overrides = _unpack_schema( + schema, schema_overrides=schema_overrides, n_expected=1 + ) + data_series: list[PySeries] = [ + pl.Series( + column_names[0], + data, + schema_overrides.get(column_names[0]), + strict=strict, + )._s + ] + data_series = _handle_columns_arg(data_series, columns=column_names) + return PyDataFrame(data_series) + + +def _sequence_of_numpy_to_pydf( + first_element: np.ndarray[Any, Any], + **kwargs: Any, +) -> PyDataFrame: + if first_element.ndim == 1: + return _sequence_of_sequence_to_pydf(first_element, **kwargs) + else: + return _sequence_of_elements_to_pydf(first_element, **kwargs) + + +def _sequence_of_pandas_to_pydf( + first_element: pd.Series[Any] | pd.Index[Any] | pd.DatetimeIndex, # noqa: ARG001 + data: Sequence[Any], + schema: SchemaDefinition | None, + schema_overrides: SchemaDict | None, + *, + strict: bool, + **kwargs: Any, # noqa: ARG001 +) -> PyDataFrame: + if schema is None: + column_names: list[str] = [] + else: + column_names, schema_overrides = _unpack_schema( + schema, schema_overrides=schema_overrides, n_expected=1 + ) + + schema_overrides = schema_overrides or {} + data_series: list[PySeries] = [] + for i, s in enumerate(data): + name = column_names[i] if column_names else s.name + pyseries = plc.pandas_to_pyseries(name=name, values=s) + dtype = schema_overrides.get(name) + if dtype is not None and dtype != pyseries.dtype(): + pyseries = pyseries.cast(dtype, strict=strict, wrap_numerical=False) + data_series.append(pyseries) + + return PyDataFrame(data_series) + + +def _sequence_of_dataclasses_to_pydf( + first_element: Any, + data: Sequence[Any], + schema: SchemaDefinition | None, + schema_overrides: SchemaDict | None, + infer_schema_length: int | None, + *, + strict: bool = True, + **kwargs: Any, # noqa: ARG001 +) -> PyDataFrame: + """Initialize DataFrame from Python dataclasses.""" + from dataclasses import asdict, astuple, fields + + ( + unpack_nested, + column_names, + schema_overrides, + overrides, + ) = _establish_dataclass_or_model_schema( + first_element, schema, schema_overrides, model_fields=None + ) + if unpack_nested: + dicts = [asdict(md) for md in data] + pydf = PyDataFrame.from_dicts( + dicts, + schema=None, + schema_overrides=None, + strict=strict, + infer_schema_length=infer_schema_length, + ) + else: + names = [f.name for f in fields(first_element)] + cls = type(first_element) + if ( + len(names) > 1 + and all( + tp.base_type() not in (Unknown, Object) and not tp.is_nested() + for tp in overrides.values() + ) + and all(type(dc) is cls for dc in data) + ): + # `astuple` deep copies all values, which is not needed for flat types. + rows = list(map(attrgetter(*names), data)) + else: + rows = [astuple(dc) for dc in data] + pydf = PyDataFrame.from_rows( + rows, # type: ignore[arg-type] + schema=overrides or None, + infer_schema_length=infer_schema_length, + ) + + if overrides: + structs = {c: tp for c, tp in overrides.items() if isinstance(tp, Struct)} + pydf = _post_apply_columns( + pydf, column_names, structs, schema_overrides, strict=strict + ) + + return pydf + + +def _sequence_of_pydantic_models_to_pydf( + first_element: Any, + data: Sequence[Any], + schema: SchemaDefinition | None, + schema_overrides: SchemaDict | None, + infer_schema_length: int | None, + *, + strict: bool, + **kwargs: Any, # noqa: ARG001 +) -> PyDataFrame: + """Initialise DataFrame from pydantic model objects.""" + import pydantic # note: must already be available in the env here + + old_pydantic = parse_version(pydantic.__version__) < (2, 0) + model_fields = list( + first_element.__fields__ + if old_pydantic + else first_element.__class__.model_fields + ) + ( + unpack_nested, + column_names, + schema_overrides, + overrides, + ) = _establish_dataclass_or_model_schema( + first_element, schema, schema_overrides, model_fields + ) + if unpack_nested: + # note: this is an *extremely* slow path, due to the requirement to + # use pydantic's 'dict()' method to properly unpack nested models + dicts = ( + [md.dict() for md in data] + if old_pydantic + else [md.model_dump(mode="python") for md in data] + ) + pydf = PyDataFrame.from_dicts( + dicts, + schema=None, + schema_overrides=None, + strict=strict, + infer_schema_length=infer_schema_length, + ) + + elif len(model_fields) > 50: + # 'from_rows' is the faster codepath for models with a lot of fields... + get_values = itemgetter(*model_fields) + rows = [get_values(md.__dict__) for md in data] + pydf = PyDataFrame.from_rows( + rows, schema=overrides, infer_schema_length=infer_schema_length + ) + else: + # ...and 'from_dicts' is faster otherwise + dicts = [md.__dict__ for md in data] + pydf = PyDataFrame.from_dicts( + dicts, + schema=overrides, + schema_overrides=None, + strict=strict, + infer_schema_length=infer_schema_length, + ) + + if overrides: + structs = {c: tp for c, tp in overrides.items() if isinstance(tp, Struct)} + pydf = _post_apply_columns( + pydf, column_names, structs, schema_overrides, strict=strict + ) + + return pydf + + +def _establish_dataclass_or_model_schema( + first_element: Any, + schema: SchemaDefinition | None, + schema_overrides: SchemaDict | None, + model_fields: list[str] | None, +) -> tuple[bool, list[str], SchemaDict, SchemaDict]: + """Shared utility code for establishing dataclasses/pydantic model cols/schema.""" + from dataclasses import asdict + + unpack_nested = False + if schema: + column_names, schema_overrides = _unpack_schema( + schema, schema_overrides=schema_overrides + ) + overrides = {col: schema_overrides.get(col, Unknown) for col in column_names} + else: + column_names = [] + overrides = { + col: (try_parse_into_dtype(tp) or Unknown) + for col, tp in try_get_type_hints(first_element.__class__).items() + if ((col in model_fields) if model_fields else (col != "__slots__")) + } + if schema_overrides: + overrides.update(schema_overrides) + elif not model_fields: + dc_fields = set(asdict(first_element)) + schema_overrides = overrides = { + nm: tp for nm, tp in overrides.items() if nm in dc_fields + } + else: + schema_overrides = overrides + + for col, tp in overrides.items(): + if tp in (Categorical, Enum): + overrides[col] = String + elif not unpack_nested and (tp.base_type() in (Unknown, Struct)): + unpack_nested = contains_nested( + getattr(first_element, col, None), + is_pydantic_model if model_fields else is_dataclass_instance, + ) + + if model_fields and len(model_fields) == len(overrides): + overrides = dict(zip(model_fields, overrides.values(), strict=True)) + + return unpack_nested, column_names, schema_overrides, overrides + + +def _include_unknowns( + schema: SchemaDict, cols: Sequence[str] +) -> MutableMapping[str, PolarsDataType]: + """Complete partial schema dict by including Unknown type.""" + return { + col: (schema.get(col, Unknown) or Unknown) # type: ignore[truthy-bool] + for col in cols + } + + +def iterable_to_pydf( + data: Iterable[Any], + schema: SchemaDefinition | None = None, + *, + schema_overrides: SchemaDict | None = None, + strict: bool = True, + orient: Orientation | None = None, + chunk_size: int | None = None, + infer_schema_length: int | None = N_INFER_DEFAULT, + rechunk: bool = True, +) -> PyDataFrame: + """Construct a PyDataFrame from an iterable/generator.""" + original_schema = schema + column_names: list[str] = [] + dtypes_by_idx: dict[int, PolarsDataType] = {} + if schema is not None: + column_names, schema_overrides = _unpack_schema( + schema, schema_overrides=schema_overrides + ) + elif schema_overrides: + _, schema_overrides = _unpack_schema(schema, schema_overrides=schema_overrides) + + if not isinstance(data, Generator): + data = iter(data) + + if orient == "col": + if column_names and schema_overrides: + dtypes_by_idx = { + idx: schema_overrides.get(col, Unknown) + for idx, col in enumerate(column_names) + } + + return pl.DataFrame( + { + (column_names[idx] if column_names else f"column_{idx}"): pl.Series( + coldata, + dtype=dtypes_by_idx.get(idx), + strict=strict, + ) + for idx, coldata in enumerate(data) + }, + )._df + + def to_frame_chunk(values: list[Any], schema: SchemaDefinition | None) -> DataFrame: + first_element = get_first_non_none(values) + row_oriented = ( + isinstance(first_element, (Sequence, Generator)) + and not isinstance(first_element, str) + ) or ( + _check_for_numpy(first_element) + and isinstance(first_element, np.ndarray) + and first_element.ndim > 0 + ) + return pl.DataFrame( + data=values, + schema=schema, + strict=strict, + orient="row" if row_oriented else None, + infer_schema_length=infer_schema_length, + schema_overrides=schema_overrides, + ) + + n_chunks = 0 + n_chunk_elems = 1_000_000 + + if chunk_size: + adaptive_chunk_size = chunk_size + elif column_names: + adaptive_chunk_size = n_chunk_elems // len(column_names) + else: + adaptive_chunk_size = None + + df: DataFrame = None # type: ignore[assignment] + chunk_size = ( + None + if infer_schema_length is None + else max(infer_schema_length, adaptive_chunk_size or 1000) + ) + while True: + values = list(islice(data, chunk_size)) + if not values: + break + frame_chunk = to_frame_chunk(values, original_schema) + if df is None: + df = frame_chunk + if not original_schema: + original_schema = list(df.schema.items()) + if chunk_size != adaptive_chunk_size: + if (n_columns := df.width) > 0: + chunk_size = adaptive_chunk_size = n_chunk_elems // n_columns + else: + df.vstack(frame_chunk, in_place=True) + n_chunks += 1 + + if df is None: + df = to_frame_chunk([], original_schema) + + if n_chunks > 0 and rechunk: + df = df.rechunk() + + return df._df + + +def _check_pandas_columns(data: pd.DataFrame, *, include_index: bool) -> None: + """Check pandas dataframe columns can be converted to polars.""" + stringified_cols: set[str] = {str(col) for col in data.columns} + stringified_index: set[str] = ( + {str(idx) for idx in data.index.names} if include_index else set() + ) + + non_unique_cols: bool = len(stringified_cols) < len(data.columns) + non_unique_indices: bool = ( + (len(stringified_index) < len(data.index.names)) if include_index else False + ) + if non_unique_cols or non_unique_indices: + msg = ( + "Pandas dataframe contains non-unique indices and/or column names. " + "Polars dataframes require unique string names for columns." + ) + raise ValueError(msg) + + overlapping_cols_and_indices: set[str] = stringified_cols & stringified_index + if len(overlapping_cols_and_indices) > 0: + msg = "Pandas indices and column names must not overlap." + raise ValueError(msg) + + +def pandas_to_pydf( + data: pd.DataFrame, + schema: SchemaDefinition | None = None, + *, + schema_overrides: SchemaDict | None = None, + strict: bool = True, + rechunk: bool = True, + nan_to_null: bool = True, + include_index: bool = False, +) -> PyDataFrame: + """Construct a PyDataFrame from a pandas DataFrame.""" + _check_pandas_columns(data, include_index=include_index) + + convert_index = include_index and not _pandas_has_default_index(data) + + if not convert_index: + if data.shape[1] == 0: + return PyDataFrame.empty_with_height(data.shape[0]) + + if all(is_simple_numpy_backed_pandas_series(data[col]) for col in data.columns): + # Convert via NumPy directly, no PyArrow needed. + return pl.DataFrame( + {str(col): data[col].to_numpy() for col in data.columns}, + schema=schema, + strict=strict, + schema_overrides=schema_overrides, + nan_to_null=nan_to_null, + )._df + + if not _PYARROW_AVAILABLE: + msg = ( + "pyarrow is required for converting a pandas dataframe to Polars, " + "unless each of its columns is a simple numpy-backed one " + "(e.g. 'int64', 'bool', 'float32' - not 'Int64')" + ) + raise ImportError(msg) + arrow_dict = {} + length = data.shape[0] + + if convert_index: + for idxcol in data.index.names: + arrow_dict[str(idxcol)] = plc.pandas_series_to_arrow( + # get_level_values accepts `int | str` + # but `index.names` returns `Hashable` + data.index.get_level_values(idxcol), # type: ignore[arg-type, unused-ignore] + nan_to_null=nan_to_null, + length=length, + ) + + for col_idx, col_data in data.items(): + arrow_dict[str(col_idx)] = plc.pandas_series_to_arrow( + col_data, nan_to_null=nan_to_null, length=length + ) + + arrow_table = pa.table(arrow_dict) + return arrow_to_pydf( + arrow_table, + schema=schema, + schema_overrides=schema_overrides, + strict=strict, + rechunk=rechunk, + ) + + +def _pandas_has_default_index(df: pd.DataFrame) -> bool: + """Identify if the pandas frame only has a default (or equivalent) index.""" + from pandas.core.indexes.range import RangeIndex + + index_cols = df.index.names + + if len(index_cols) > 1 or index_cols not in ([None], [""]): + # not default: more than one index, or index is named + return False + elif df.index.equals(RangeIndex(start=0, stop=len(df), step=1)): + # is default: simple range index + return True + else: + # finally, is the index _equivalent_ to a default unnamed + # integer index with frame data that was previously sorted + return str(df.index.dtype).startswith("int") and bool( + (df.index.sort_values() == np.arange(len(df))).all() + ) + + +def arrow_to_pydf( + data: pa.Table | pa.RecordBatch, + schema: SchemaDefinition | None = None, + *, + schema_overrides: SchemaDict | None = None, + strict: bool = True, + rechunk: bool = True, +) -> PyDataFrame: + """Construct a PyDataFrame from an Arrow Table or RecordBatch.""" + column_names, schema_overrides = _unpack_schema( + (schema or data.schema.names), schema_overrides=schema_overrides + ) + try: + if column_names != data.schema.names: + data = data.rename_columns(column_names) + except pa.ArrowInvalid as e: + msg = "dimensions of columns arg must match data dimensions" + raise ValueError(msg) from e + + batches: list[pa.RecordBatch] + if isinstance(data, pa.RecordBatch): + batches = [data] + elif data.num_columns == 0: + return PyDataFrame.empty_with_height(data.num_rows) + else: + batches = data.to_batches() + + # supply the arrow schema so the metadata is intact + pydf = PyDataFrame.from_arrow_record_batches(batches, data.schema) + + if rechunk: + pydf = pydf.rechunk() + + if schema_overrides is not None: + pydf = _post_apply_columns( + pydf, + column_names, + schema_overrides=schema_overrides, + strict=strict, + ) + + return pydf + + +def numpy_to_pydf( + data: np.ndarray[Any, Any], + schema: SchemaDefinition | None = None, + *, + schema_overrides: SchemaDict | None = None, + orient: Orientation | None = None, + strict: bool = True, + nan_to_null: bool = False, +) -> PyDataFrame: + """Construct a PyDataFrame from a NumPy ndarray (including structured ndarrays).""" + shape = data.shape + two_d = len(shape) == 2 + + if data.dtype.names is not None: + structured_array, orient = True, "col" + record_names = list(data.dtype.names) + n_columns = len(record_names) + for nm in record_names: + shape = data[nm].shape + if not schema: + schema = record_names + else: + # Unpack columns + structured_array, record_names = False, [] + if shape == (0,): + n_columns = 0 + + elif len(shape) == 1: + n_columns = 1 + + elif len(shape) == 2: + if shape[1] == 0: + return PyDataFrame.empty_with_height(shape[0]) + + if orient is None and schema is None: + # default convention; first axis is rows, second axis is columns + n_columns = shape[1] + orient = "row" + + elif orient is None and schema is not None: + # infer orientation from 'schema' param; if square array + # we check the flags to establish row/column major order + n_schema_cols = len(schema) + if n_schema_cols == shape[0] and n_schema_cols != shape[1]: + orient = "col" + n_columns = shape[0] + elif data.flags["F_CONTIGUOUS"] and shape[0] == shape[1]: + orient = "col" + n_columns = n_schema_cols + else: + orient = "row" + n_columns = shape[1] + + elif orient == "row": + n_columns = shape[1] + elif orient == "col": + n_columns = shape[0] + else: + msg = f"`orient` must be one of {{'col', 'row', None}}, got {orient!r}" + raise ValueError(msg) + else: + if shape == (): + msg = "cannot create DataFrame from zero-dimensional array" + else: + msg = f"cannot create DataFrame from array with more than two dimensions; shape = {shape}" + raise ValueError(msg) + + if schema is not None and len(schema) != n_columns: + if (n_schema_cols := len(schema)) != 1: + msg = f"dimensions of `schema` ({n_schema_cols}) must match data dimensions ({n_columns})" + raise ValueError(msg) + n_columns = n_schema_cols + + column_names, schema_overrides = _unpack_schema( + schema, schema_overrides=schema_overrides, n_expected=n_columns + ) + + # Convert data to series + if structured_array: + data_series = [ + pl.Series( + name=series_name, + values=data[record_name], + dtype=schema_overrides.get(record_name), + strict=strict, + nan_to_null=nan_to_null, + )._s + for series_name, record_name in zip(column_names, record_names, strict=True) + ] + elif shape == (0,) and n_columns == 0: + data_series = [] + + elif len(shape) == 1: + data_series = [ + pl.Series( + name=column_names[0], + values=data, + dtype=schema_overrides.get(column_names[0]), + strict=strict, + nan_to_null=nan_to_null, + )._s + ] + else: + if orient == "row": + data_series = [ + pl.Series( + name=column_names[i], + values=( + data + if two_d and n_columns == 1 and shape[1] > 1 + else data[:, i] + ), + dtype=schema_overrides.get(column_names[i]), + strict=strict, + nan_to_null=nan_to_null, + )._s + for i in range(n_columns) + ] + else: + data_series = [ + pl.Series( + name=column_names[i], + values=( + data if two_d and n_columns == 1 and shape[1] > 1 else data[i] + ), + dtype=schema_overrides.get(column_names[i]), + strict=strict, + nan_to_null=nan_to_null, + )._s + for i in range(n_columns) + ] + + data_series = _handle_columns_arg(data_series, columns=column_names) + return PyDataFrame(data_series) + + +def series_to_pydf( + data: Series, + schema: SchemaDefinition | None = None, + schema_overrides: SchemaDict | None = None, + *, + strict: bool = True, +) -> PyDataFrame: + """Construct a PyDataFrame from a Polars Series.""" + if schema is None and schema_overrides is None: + return PyDataFrame([data._s]) + + data_series = [data._s] + series_name = [s.name() for s in data_series] + column_names, schema_overrides = _unpack_schema( + schema or series_name, schema_overrides=schema_overrides, n_expected=1 + ) + if schema_overrides: + new_dtype = next(iter(schema_overrides.values())) + if new_dtype != data.dtype: + data_series[0] = data_series[0].cast( + new_dtype, strict=strict, wrap_numerical=False + ) + + data_series = _handle_columns_arg(data_series, columns=column_names) + return PyDataFrame(data_series) + + +def dataframe_to_pydf( + data: DataFrame, + schema: SchemaDefinition | None = None, + *, + schema_overrides: SchemaDict | None = None, + strict: bool = True, +) -> PyDataFrame: + """Construct a PyDataFrame from an existing Polars DataFrame.""" + if schema is None and schema_overrides is None: + return data._df.clone() + + data_series = {c.name: c._s for c in data} + column_names, schema_overrides = _unpack_schema( + schema or data.columns, schema_overrides=schema_overrides + ) + if schema_overrides: + existing_schema = data.schema + for name, new_dtype in schema_overrides.items(): + if new_dtype != existing_schema[name]: + data_series[name] = data_series[name].cast( + new_dtype, strict=strict, wrap_numerical=False + ) + + series_cols = _handle_columns_arg(list(data_series.values()), columns=column_names) + return PyDataFrame(series_cols) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/other.py b/hug/lib/python3.12/site-packages/polars/_utils/construction/other.py new file mode 100644 index 0000000000000000000000000000000000000000..dd58813b318825fe12614e5b249dacd8412752d4 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/construction/other.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from polars._dependencies import pyarrow as pa +from polars._utils.construction.utils import get_first_non_none + +if TYPE_CHECKING: + from polars._dependencies import pandas as pd + + +def pandas_series_to_arrow( + values: pd.Series[Any] | pd.Index[Any], + *, + length: int | None = None, + nan_to_null: bool = True, +) -> pa.Array: + """ + Convert a pandas Series to an Arrow Array. + + Parameters + ---------- + values : :class:`pandas.Series` or :class:`pandas.Index`. + Series to convert to arrow + nan_to_null : bool, default = True + Interpret `NaN` as missing values. + length : int, optional + in case all values are null, create a null array of this length. + if unset, length is inferred from values. + + Returns + ------- + :class:`pyarrow.Array` + """ + dtype = getattr(values, "dtype", None) + if dtype == "object": + first_non_none = get_first_non_none(values.values) # type: ignore[arg-type] + if isinstance(first_non_none, str): + return pa.array(values, pa.large_utf8(), from_pandas=nan_to_null) + elif first_non_none is None: + return pa.nulls(length or len(values), pa.large_utf8()) + return pa.array(values, from_pandas=nan_to_null) + elif dtype: + return pa.array(values, from_pandas=nan_to_null) + else: + # Pandas Series is actually a Pandas DataFrame when the original DataFrame + # contains duplicated columns and a duplicated column is requested with df["a"]. + msg = "duplicate column names found: " + raise ValueError( + msg, + f"{values.columns.tolist()!s}", # type: ignore[union-attr] + ) + + +def coerce_arrow(array: pa.Array) -> pa.Array: + """...""" + import pyarrow.compute as pc + + if hasattr(array, "num_chunks") and array.num_chunks > 1: + # small integer keys can often not be combined, so let's already cast + # to the uint32 used by polars + if pa.types.is_dictionary(array.type) and ( + pa.types.is_int8(array.type.index_type) + or pa.types.is_uint8(array.type.index_type) + or pa.types.is_int16(array.type.index_type) + or pa.types.is_uint16(array.type.index_type) + or pa.types.is_int32(array.type.index_type) + ): + array = pc.cast( + array, pa.dictionary(pa.uint32(), pa.large_string()) + ).combine_chunks() + return array diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/series.py b/hug/lib/python3.12/site-packages/polars/_utils/construction/series.py new file mode 100644 index 0000000000000000000000000000000000000000..b5ca43ec4d428feadbf18e50946b56dcd602ade5 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/construction/series.py @@ -0,0 +1,588 @@ +from __future__ import annotations + +import contextlib +from collections.abc import Generator, Iterator, Mapping +from datetime import date, datetime, time, timedelta +from enum import Enum as PyEnum +from itertools import islice +from typing import ( + TYPE_CHECKING, + Any, +) + +import polars._reexport as pl +import polars._utils.construction as plc +from polars._dependencies import _PYARROW_AVAILABLE, _check_for_numpy +from polars._dependencies import numpy as np +from polars._dependencies import pyarrow as pa +from polars._utils.construction.dataframe import _sequence_of_dict_to_pydf +from polars._utils.construction.utils import ( + get_first_non_none, + is_dataclass_instance, + is_namedtuple, + is_pydantic_model, + is_simple_numpy_backed_pandas_series, + is_sqlalchemy_row, +) +from polars._utils.various import ( + range_to_series, +) +from polars._utils.wrap import wrap_s +from polars.datatypes import ( + Array, + BaseExtension, + Boolean, + Categorical, + Date, + Datetime, + Decimal, + Duration, + Enum, + List, + Map, + Null, + Object, + String, + Struct, + Time, + Unknown, + dtype_to_py_type, + is_polars_dtype, + numpy_char_code_to_dtype, + parse_into_dtype, + try_parse_into_dtype, +) +from polars.datatypes.constructor import ( + numpy_type_to_constructor, + numpy_values_and_dtype, + polars_type_to_constructor, + py_type_to_constructor, +) + +with contextlib.suppress(ImportError): # Module not available when building docs + from polars._plr import PySeries + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable, Sequence + + from polars import DataFrame, Series + from polars._dependencies import pandas as pd + from polars._typing import PolarsDataType + + +def sequence_to_pyseries( + name: str, + values: Sequence[Any], + dtype: PolarsDataType | None = None, + *, + strict: bool = True, + nan_to_null: bool = False, +) -> PySeries: + """Construct a PySeries from a sequence.""" + python_dtype: type | None = None + + if isinstance(dtype, BaseExtension): + storage = dtype.ext_storage() + pys = sequence_to_pyseries( + name, values, storage, strict=strict, nan_to_null=nan_to_null + ) + return pys.ext_to(dtype) + + if isinstance(values, range): + return range_to_series(name, values, dtype=dtype)._s + + # empty sequence + if len(values) == 0 and dtype is None: + # if dtype for empty sequence could be guessed + # (e.g comparisons between self and other), default to Null + dtype = Null + + # lists defer to subsequent handling; identify nested type + elif dtype in (List, Array): + python_dtype = list + + # infer temporal type handling + py_temporal_types = {date, datetime, timedelta, time} + pl_temporal_types = {Date, Datetime, Duration, Time} + + value = get_first_non_none(values) + if value is not None: + if ( + is_dataclass_instance(value) + or is_pydantic_model(value) + or is_namedtuple(value.__class__) + or is_sqlalchemy_row(value) + ) and dtype != Object: + return pl.DataFrame(values).to_struct(name)._s + elif ( + not isinstance(value, dict) and isinstance(value, Mapping) + ) and dtype != Object: + return _sequence_of_dict_to_pydf( + value, + data=values, + strict=strict, + schema_overrides=None, + infer_schema_length=None, + schema=None, + ).to_struct(name, []) + elif isinstance(value, range) and dtype is None: + values = [range_to_series("", v) for v in values] + else: + # for temporal dtypes: + # * if the values are integer, we take the physical branch. + # * if the values are python types, take the temporal branch. + # * if the values are ISO-8601 strings, init then convert via strptime. + # * if the values are floats/other dtypes, this is an error. + if dtype in py_temporal_types and isinstance(value, int): + dtype = parse_into_dtype(dtype) # construct from integer + elif ( + dtype in pl_temporal_types or type(dtype) in pl_temporal_types + ) and not isinstance(value, int): + python_dtype = dtype_to_py_type(dtype) # type: ignore[arg-type] + + # if values are enums, infer and load the appropriate dtype/values + if issubclass(type(value), PyEnum): + if dtype is None and python_dtype is None: + with contextlib.suppress(TypeError): + dtype = Enum(type(value)) + if not isinstance(value, (str, int)): + values = [v.value for v in values] + + # physical branch + # flat data + if ( + dtype is not None + and is_polars_dtype(dtype) + and not dtype.is_nested() + and dtype != Unknown + and (python_dtype is None) + ): + constructor = polars_type_to_constructor(dtype) + pyseries = _construct_series_with_fallbacks( + constructor, name, values, dtype, strict=strict + ) + if dtype in ( + Date, + Datetime, + Duration, + Time, + Boolean, + Categorical, + Enum, + ) or isinstance(dtype, (Categorical, Decimal)): + if pyseries.dtype() != dtype: + pyseries = pyseries.cast(dtype, strict=strict, wrap_numerical=False) + + # Uninstanced Decimal is a bit special and has various inference paths + if dtype == Decimal: + if pyseries.dtype() == String: + pyseries = pyseries.str_to_decimal_infer(inference_length=0) + elif pyseries.dtype().is_float(): + # Go through string so we infer an appropriate scale. + pyseries = pyseries.cast( + String, strict=strict, wrap_numerical=False + ).str_to_decimal_infer(inference_length=0) + elif pyseries.dtype().is_integer() or pyseries.dtype() == Null: + pyseries = pyseries.cast( + Decimal(scale=0), strict=strict, wrap_numerical=False + ) + elif not isinstance(pyseries.dtype(), Decimal): + msg = f"can't convert {pyseries.dtype()} to Decimal" + raise TypeError(msg) + + return pyseries + + elif isinstance(dtype, Map): + # A dict is otherwise inferred as a Struct, so the Map dtype has to drive this. + return PySeries.new_from_any_values_and_dtype( + name, values, dtype, strict=strict + ) + + elif dtype == Map: + msg = "Map requires a key and a value type, e.g. `pl.Map(pl.String, pl.Int64)`" + raise TypeError(msg) + + elif dtype == Struct: + # This is very bad. Goes via rows? And needs to do outer nullability separate. + # It also has two data passes. + # TODO: eventually go into struct builder + struct_schema = dtype.to_schema() if isinstance(dtype, Struct) else None + empty = {} # type: ignore[var-annotated] + + data = [] + invalid = [] + for i, v in enumerate(values): + if v is None: + invalid.append(i) + data.append(empty) + else: + data.append(v) + + return plc.sequence_to_pydf( + data=data, + schema=struct_schema, + orient="row", + ).to_struct(name, invalid) + + if python_dtype is None: + if value is None: + constructor = polars_type_to_constructor(Null) + return constructor(name, values, strict) + + # generic default dtype + python_dtype = type(value) + + # temporal branch + if issubclass(python_dtype, tuple(py_temporal_types)): + if dtype is None: + dtype = parse_into_dtype(python_dtype) # construct from integer + elif dtype in py_temporal_types: + dtype = parse_into_dtype(dtype) + + values_dtype = None if value is None else try_parse_into_dtype(type(value)) + if values_dtype is not None and values_dtype.is_float(): + msg = f"'float' object cannot be interpreted as a {python_dtype.__name__!r}" + raise TypeError( + # we do not accept float values as temporal; if this is + # required, the caller should explicitly cast to int first. + msg + ) + + # We use the AnyValue builder to create the datetime array + # We store the values internally as UTC and set the timezone + py_series = PySeries.new_from_any_values(name, values, strict) + + time_unit = getattr(dtype, "time_unit", None) + time_zone = getattr(dtype, "time_zone", None) + + if dtype.is_temporal() and values_dtype == String and dtype != Duration: + s = wrap_s(py_series).str.strptime(dtype, strict=strict) # type: ignore[arg-type] + elif time_unit is not None and values_dtype != Date: + s = wrap_s(py_series).dt.cast_time_unit(time_unit) + else: + s = wrap_s(py_series) + + if (values_dtype == Date) & (dtype == Datetime): + s = s.cast(Datetime(time_unit or "us")) + + if dtype == Datetime and time_zone is not None: + return s.dt.convert_time_zone(time_zone)._s + return s._s + + elif ( + _check_for_numpy(value) + and isinstance(value, np.ndarray) + and len(value.shape) == 1 + ): + n_elems = len(value) + if all(len(v) == n_elems for v in values): + # can take (much) faster path if all lists are the same length + masked = any(isinstance(v, np.ma.MaskedArray) for v in values) + return numpy_to_pyseries( + name, + (np.ma.vstack if masked else np.vstack)(values), + strict=strict, + nan_to_null=nan_to_null, + ) + else: + return PySeries.new_series_list( + name, + [ + numpy_to_pyseries("", v, strict=strict, nan_to_null=nan_to_null) + for v in values + ], + strict, + ) + + elif python_dtype in (list, tuple): + if dtype is None: + return PySeries.new_from_any_values(name, values, strict=strict) + elif dtype == Object: + return PySeries.new_object(name, values, strict) + else: + if (inner_dtype := getattr(dtype, "inner", None)) is not None: + pyseries_list = [ + None + if value is None + else sequence_to_pyseries( + "", + value, + inner_dtype, + strict=strict, + nan_to_null=nan_to_null, + ) + for value in values + ] + pyseries = PySeries.new_series_list(name, pyseries_list, strict) + else: + pyseries = PySeries.new_from_any_values_and_dtype( + name, values, dtype, strict=strict + ) + if dtype != pyseries.dtype(): + pyseries = pyseries.cast(dtype, strict=False, wrap_numerical=False) + return pyseries + + elif python_dtype == pl.Series: + return PySeries.new_series_list( + name, [v._s if v is not None else None for v in values], strict + ) + + elif python_dtype == PySeries: + return PySeries.new_series_list(name, values, strict) + else: + constructor = py_type_to_constructor(python_dtype) + if constructor == PySeries.new_object: + try: + srs = PySeries.new_from_any_values(name, values, strict) + if _check_for_numpy(python_dtype, check_type=False) and isinstance( + np.bool_(True), np.generic + ): + dtype = numpy_char_code_to_dtype(np.dtype(python_dtype).char) + return srs.cast(dtype, strict=strict, wrap_numerical=False) + else: + return srs + + except RuntimeError: + return PySeries.new_from_any_values(name, values, strict=strict) + + return _construct_series_with_fallbacks( + constructor, name, values, dtype, strict=strict + ) + + +def _construct_series_with_fallbacks( + constructor: Callable[[str, Sequence[Any], bool], PySeries], + name: str, + values: Sequence[Any], + dtype: PolarsDataType | None, + *, + strict: bool, +) -> PySeries: + """Construct Series, with fallbacks for basic type mismatch (eg: bool/int).""" + try: + return constructor(name, values, strict) + except (TypeError, OverflowError) as e: + # # This retry with i64 is related to https://github.com/pola-rs/polars/issues/17231 + # # Essentially, when given a [0, u64::MAX] then it would Overflow. + if ( + isinstance(e, OverflowError) + and dtype is None + and constructor == PySeries.new_opt_i64 + ): + return _construct_series_with_fallbacks( + PySeries.new_opt_u64, name, values, dtype, strict=strict + ) + elif dtype is None: + return PySeries.new_from_any_values(name, values, strict=strict) + else: + return PySeries.new_from_any_values_and_dtype( + name, values, dtype, strict=strict + ) + + +def iterable_to_pyseries( + name: str, + values: Iterable[Any], + dtype: PolarsDataType | None = None, + *, + chunk_size: int = 1_000_000, + strict: bool = True, +) -> PySeries: + """Construct a PySeries from an iterable/generator.""" + if not isinstance(values, (Generator, Iterator)): + values = iter(values) + + def to_series_chunk(values: list[Any], dtype: PolarsDataType | None) -> Series: + return pl.Series( + name=name, + values=values, + dtype=dtype, + strict=strict, + ) + + n_chunks = 0 + series: Series = None # type: ignore[assignment] + while True: + slice_values = list(islice(values, chunk_size)) + if not slice_values: + break + schunk = to_series_chunk(slice_values, dtype) + if series is None: + series = schunk + dtype = series.dtype + else: + series.append(schunk) + n_chunks += 1 + + if series is None: + series = to_series_chunk([], dtype) + if n_chunks > 0: + series.rechunk(in_place=True) + + return series._s + + +def pandas_to_pyseries( + name: str, + values: pd.Series[Any] | pd.Index[Any] | pd.DatetimeIndex, + dtype: PolarsDataType | None = None, + *, + strict: bool = True, + nan_to_null: bool = True, +) -> PySeries: + """Construct a PySeries from a pandas Series or DatetimeIndex.""" + if not name and values.name is not None: + name = str(values.name) + if is_simple_numpy_backed_pandas_series(values): + return pl.Series( + name, values.to_numpy(), dtype=dtype, nan_to_null=nan_to_null, strict=strict + )._s + if not _PYARROW_AVAILABLE: + msg = ( + "pyarrow is required for converting a pandas series to Polars, " + "unless it is a simple numpy-backed one " + "(e.g. 'int64', 'bool', 'float32' - not 'Int64')" + ) + raise ImportError(msg) + return arrow_to_pyseries( + name, + plc.pandas_series_to_arrow(values, nan_to_null=nan_to_null), + dtype=dtype, + strict=strict, + ) + + +def arrow_to_pyseries( + name: str, + values: pa.Array, + dtype: PolarsDataType | None = None, + *, + strict: bool = True, + rechunk: bool = True, +) -> PySeries: + """Construct a PySeries from an Arrow array.""" + array = plc.coerce_arrow(values) + + # special handling of empty categorical arrays + if ( + len(array) == 0 + and isinstance(array.type, pa.DictionaryType) + and array.type.value_type + in ( + pa.utf8(), + pa.large_utf8(), + ) + ): + pys = pl.Series(name, [], dtype=Categorical)._s + + elif not hasattr(array, "num_chunks"): + pys = PySeries.from_arrow(name, array) + else: + if array.num_chunks > 1: + # somehow going through ffi with a structarray + # returns the first chunk every time + if isinstance(array.type, pa.StructType): + pys = PySeries.from_arrow(name, array.combine_chunks()) + else: + it = array.iterchunks() + pys = PySeries.from_arrow(name, next(it)) + for a in it: + pys.append(PySeries.from_arrow(name, a)) + elif array.num_chunks == 0: + pys = PySeries.from_arrow(name, pa.nulls(0, type=array.type)) + else: + pys = PySeries.from_arrow(name, array.chunks[0]) + + if rechunk: + pys.rechunk(in_place=True) + + return ( + pys.cast(dtype, strict=strict, wrap_numerical=False) + if dtype is not None + else pys + ) + + +def numpy_to_pyseries( + name: str, + values: np.ndarray[Any, Any], + *, + strict: bool = True, + nan_to_null: bool = False, +) -> PySeries: + """Construct a PySeries from a numpy array.""" + if not values.dtype.isnative: + # Only native byte order is supported, so swap to a native-order copy. + values = values.astype(values.dtype.newbyteorder("=")) + # Require aligned, C-contiguous, >=1d; an unaligned view would otherwise panic. + values = np.atleast_1d(values) + values = np.require(values, requirements=["A", "C"]) + + if values.ndim == 1: + mask = np.ma.getmask(values) + values, dtype = numpy_values_and_dtype(np.ma.getdata(values)) + constructor = numpy_type_to_constructor(values, dtype) + py_s = constructor( + name, + values, + nan_to_null if dtype in (np.float16, np.float32, np.float64) else strict, + ) + if mask is not np.ma.nomask and mask.any(): + py_s = wrap_s(py_s).set(pl.Series(mask), None)._s + return py_s + else: + original_shape = values.shape + values_1d = values.reshape(-1) + + from polars.series.utils import _with_no_check_length + + py_s = _with_no_check_length( + lambda: numpy_to_pyseries( + name, + values_1d, + strict=strict, + nan_to_null=nan_to_null, + ) + ) + return wrap_s(py_s).reshape(original_shape)._s + + +def series_to_pyseries( + name: str | None, + values: Series, + *, + dtype: PolarsDataType | None = None, + strict: bool = True, +) -> PySeries: + """Construct a new PySeries from a Polars Series.""" + s = values.clone() + if dtype is not None and dtype != s.dtype: + s = s.cast(dtype, strict=strict) + if name is not None: + s = s.alias(name) + return s._s + + +def dataframe_to_pyseries( + name: str | None, + values: DataFrame, + *, + dtype: PolarsDataType | None = None, + strict: bool = True, +) -> PySeries: + """Construct a new PySeries from a Polars DataFrame.""" + if values.width > 1: + name = name or "" + s = values.to_struct(name) + elif values.width == 1: + s = values.to_series() + if name is not None: + s = s.alias(name) + else: + msg = "cannot initialize Series from DataFrame without any columns" + raise TypeError(msg) + + if dtype is not None and dtype != s.dtype: + s = s.cast(dtype, strict=strict) + + return s._s diff --git a/hug/lib/python3.12/site-packages/polars/_utils/construction/utils.py b/hug/lib/python3.12/site-packages/polars/_utils/construction/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..973743a03614fe2cb1984518518bf77148245c42 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/construction/utils.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from collections.abc import Sequence +from functools import lru_cache +from typing import TYPE_CHECKING, Any, Final, get_type_hints + +import polars as pl +from polars._dependencies import _check_for_pydantic, dataclasses, pydantic + +if TYPE_CHECKING: + from collections.abc import Callable + + import pandas as pd + +PANDAS_SIMPLE_NUMPY_DTYPES: Final[set[str]] = { + "int64", + "int32", + "int16", + "int8", + "uint64", + "uint32", + "uint16", + "uint8", + "float64", + "float32", + "datetime64[ms]", + "datetime64[us]", + "datetime64[ns]", + "timedelta64[ms]", + "timedelta64[us]", + "timedelta64[ns]", + "bool", +} + + +def _get_annotations(obj: type) -> dict[str, Any]: + return getattr(obj, "__annotations__", {}) + + +def try_get_type_hints(obj: type) -> dict[str, Any]: + try: + # often the same as obj.__annotations__, but handles forward references + # encoded as string literals, adds Optional[t] if a default value equal + # to None is set and recursively replaces 'Annotated[T, ...]' with 'T'. + return get_type_hints(obj) + except TypeError: + # fallback on edge-cases (eg: InitVar inference on python 3.10). + return _get_annotations(obj) + + +@lru_cache(64) +def is_namedtuple(cls: Any, *, annotated: bool = False) -> bool: + """Check if given class derives from NamedTuple.""" + if all(hasattr(cls, attr) for attr in ("_fields", "_field_defaults", "_replace")): + if not isinstance(cls._fields, property): + if not annotated or len(cls.__annotations__) == len(cls._fields): + return all(isinstance(fld, str) for fld in cls._fields) + return False + + +def is_dataclass_instance(value: Any) -> bool: + """Check if value is a dataclass instance (`is_dataclass` also matches classes).""" + return dataclasses.is_dataclass(value) and not isinstance(value, type) + + +def is_pydantic_model(value: Any) -> bool: + """Check if value derives from a pydantic.BaseModel.""" + return _check_for_pydantic(value) and isinstance(value, pydantic.BaseModel) + + +def is_sqlalchemy_row(value: Any) -> bool: + """Check if value is an instance of a SQLAlchemy sequence or mapping object.""" + return getattr(value, "__module__", "").startswith("sqlalchemy.") and isinstance( + value, Sequence + ) + + +def get_first_non_none(values: Sequence[Any | None] | pl.Series) -> Any: + """ + Return the first value from a sequence that isn't None. + + If sequence doesn't contain non-None values, return None. + """ + if isinstance(values, pl.Series): + if values.dtype == pl.Null or values.null_count() == len(values): + return None + + return next((v for v in values if v is not None), None) + + +def nt_unpack(obj: Any) -> Any: + """Recursively unpack a nested NamedTuple.""" + if isinstance(obj, dict): + return {key: nt_unpack(value) for key, value in obj.items()} + elif isinstance(obj, list): + return [nt_unpack(value) for value in obj] + elif is_namedtuple(obj.__class__): + return {key: nt_unpack(value) for key, value in obj._asdict().items()} + elif isinstance(obj, tuple): + return tuple(nt_unpack(value) for value in obj) + else: + return obj + + +def contains_nested(value: Any, is_nested: Callable[[Any], bool]) -> bool: + """Determine if value contains (or is) nested structured data.""" + if is_nested(value): + return True + elif isinstance(value, dict): + return any(contains_nested(v, is_nested) for v in value.values()) + elif isinstance(value, (list, tuple)): + return any(contains_nested(v, is_nested) for v in value) + return False + + +def is_simple_numpy_backed_pandas_series( + series: pd.Series[Any] | pd.Index[Any] | pd.DatetimeIndex, +) -> bool: + if len(series.shape) > 1: + # Pandas Series is actually a Pandas DataFrame when the original DataFrame + # contains duplicated columns and a duplicated column is requested with df["a"]. + msg = f"duplicate column names found: {series.columns.tolist()!s}" # type: ignore[union-attr] + raise ValueError(msg) + return (str(series.dtype) in PANDAS_SIMPLE_NUMPY_DTYPES) or ( + series.dtype == "object" + and not series.hasnans + and not series.empty + and isinstance(next(iter(series)), str) + ) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/convert.py b/hug/lib/python3.12/site-packages/polars/_utils/convert.py new file mode 100644 index 0000000000000000000000000000000000000000..73c7bc61c2f4446fe0909acb1564291bb91ac804 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/convert.py @@ -0,0 +1,224 @@ +from __future__ import annotations + +from datetime import UTC, datetime, time, timedelta +from decimal import Context +from functools import lru_cache +from typing import ( + TYPE_CHECKING, + Any, + NoReturn, + overload, +) +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError + +from polars._utils.constants import ( + EPOCH, + EPOCH_DATE, + EPOCH_UTC, + MS_PER_SECOND, + NS_PER_SECOND, + SECONDS_PER_DAY, + SECONDS_PER_HOUR, + US_PER_SECOND, +) + +if TYPE_CHECKING: + from collections.abc import Callable + from datetime import date, tzinfo + from decimal import Decimal + + from polars._typing import TimeUnit + + +@overload +def parse_as_duration_string(td: None) -> None: ... + + +@overload +def parse_as_duration_string(td: timedelta | str) -> str: ... + + +def parse_as_duration_string(td: timedelta | str | None) -> str | None: + """Parse duration input as a Polars duration string.""" + if td is None or isinstance(td, str): + return td + return _timedelta_to_duration_string(td) + + +def _timedelta_to_duration_string(td: timedelta) -> str: + """Convert a Python timedelta object to a Polars duration string.""" + # Positive duration + if td.days >= 0: + d = f"{td.days}d" if td.days != 0 else "" + s = f"{td.seconds}s" if td.seconds != 0 else "" + us = f"{td.microseconds}us" if td.microseconds != 0 else "" + # Negative, whole days + elif td.seconds == 0 and td.microseconds == 0: + return f"{td.days}d" + # Negative, other + else: + corrected_d = td.days + 1 + corrected_seconds = SECONDS_PER_DAY - (td.seconds + (td.microseconds > 0)) + d = f"{corrected_d}d" if corrected_d != 0 else "-" + s = f"{corrected_seconds}s" if corrected_seconds != 0 else "" + us = f"{10**6 - td.microseconds}us" if td.microseconds != 0 else "" + + return f"{d}{s}{us}" + + +def negate_duration_string(duration: str) -> str: + """Negate a Polars duration string.""" + if duration.startswith("-"): + return duration[1:] + else: + return f"-{duration}" + + +def date_to_int(d: date) -> int: + """Convert a Python time object to an integer.""" + return (d - EPOCH_DATE).days + + +def time_to_int(t: time) -> int: + """Convert a Python time object to an integer.""" + t = t.replace(tzinfo=UTC) + seconds = t.hour * SECONDS_PER_HOUR + t.minute * 60 + t.second + microseconds = t.microsecond + return seconds * NS_PER_SECOND + microseconds * 1_000 + + +def datetime_to_int(dt: datetime, time_unit: TimeUnit) -> int: + """Convert a Python datetime object to an integer.""" + # Make sure to use UTC rather than system time zone + if dt.tzinfo is None: + dt = dt.replace(tzinfo=UTC) + + td = dt - EPOCH_UTC + seconds = td.days * SECONDS_PER_DAY + td.seconds + microseconds = dt.microsecond + + if time_unit == "us": + return seconds * US_PER_SECOND + microseconds + elif time_unit == "ns": + return seconds * NS_PER_SECOND + microseconds * 1_000 + elif time_unit == "ms": + return seconds * MS_PER_SECOND + microseconds // 1_000 + else: + _raise_invalid_time_unit(time_unit) + + +def timedelta_to_int(td: timedelta, time_unit: TimeUnit) -> int: + """Convert a Python timedelta object to an integer.""" + seconds = td.days * SECONDS_PER_DAY + td.seconds + microseconds = td.microseconds + + if time_unit == "us": + return seconds * US_PER_SECOND + microseconds + elif time_unit == "ns": + return seconds * NS_PER_SECOND + microseconds * 1_000 + elif time_unit == "ms": + return seconds * MS_PER_SECOND + microseconds // 1_000 + else: + _raise_invalid_time_unit(time_unit) + + +@lru_cache(256) +def to_py_date(value: int | float) -> date: + """Convert an integer or float to a Python date object.""" + return EPOCH_DATE + timedelta(days=value) + + +def to_py_time(value: int) -> time: + """Convert an integer to a Python time object.""" + # Fast path for 00:00 + if value == 0: + return time() + + seconds, nanoseconds = divmod(value, NS_PER_SECOND) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + return time( + hour=hours, minute=minutes, second=seconds, microsecond=nanoseconds // 1_000 + ) + + +def to_py_datetime( + value: int | float, + time_unit: TimeUnit, + time_zone: str | None = None, +) -> datetime: + """Convert an integer or float to a Python datetime object.""" + if time_unit == "us": + td = timedelta(microseconds=value) + elif time_unit == "ns": + td = timedelta(microseconds=value // 1_000) + elif time_unit == "ms": + td = timedelta(milliseconds=value) + else: + _raise_invalid_time_unit(time_unit) + + if time_zone is None: + return EPOCH + td + else: + dt = EPOCH_UTC + td + return _localize_datetime(dt, time_zone) + + +def _localize_datetime(dt: datetime, time_zone: str) -> datetime: + # zone info installation should already be checked + tz: ZoneInfo | tzinfo + try: + tz = ZoneInfo(time_zone) + except ZoneInfoNotFoundError: + # try fixed offset, which is not supported by ZoneInfo + tz = _parse_fixed_tz_offset(time_zone) + + return dt.astimezone(tz) + + +# cache here as we have a single tz per column +# and this function will be called on every conversion +@lru_cache(16) +def _parse_fixed_tz_offset(offset: str) -> tzinfo: + try: + # use fromisoformat to parse the offset + dt_offset = datetime.fromisoformat("2000-01-01T00:00:00" + offset) + + # alternatively, we parse the offset ourselves extracting hours and + # minutes, then we can construct: + # tzinfo=timezone(timedelta(hours=..., minutes=...)) + except ValueError: + msg = f"unexpected time zone offset: {offset!r}" + raise ValueError(msg) from None + + return dt_offset.tzinfo # type: ignore[return-value] + + +def to_py_timedelta(value: int | float, time_unit: TimeUnit) -> timedelta: + """Convert an integer or float to a Python timedelta object.""" + if time_unit == "us": + return timedelta(microseconds=value) + elif time_unit == "ns": + return timedelta(microseconds=value // 1_000) + elif time_unit == "ms": + return timedelta(milliseconds=value) + else: + _raise_invalid_time_unit(time_unit) + + +def to_py_decimal(prec: int, value: str) -> Decimal: + """Convert decimal components to a Python Decimal object.""" + return _create_decimal_with_prec(prec)(value) + + +@lru_cache(None) +def _create_decimal_with_prec( + precision: int, +) -> Callable[[str], Decimal]: + # pre-cache contexts so we don't have to spend time on recreating them every time + return Context(prec=precision).create_decimal + + +def _raise_invalid_time_unit(time_unit: Any) -> NoReturn: + msg = f"`time_unit` must be one of {{'ms', 'us', 'ns'}}, got {time_unit!r}" + raise ValueError(msg) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/deprecation.py b/hug/lib/python3.12/site-packages/polars/_utils/deprecation.py new file mode 100644 index 0000000000000000000000000000000000000000..8147ddbe916689a82c1c6aa1e2959663467423eb --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/deprecation.py @@ -0,0 +1,373 @@ +from __future__ import annotations + +import ast +import inspect +import sys +from collections import defaultdict +from collections.abc import Sequence +from functools import wraps +from pathlib import Path +from typing import TYPE_CHECKING, Any, TypeVar, get_args + +from polars._typing import DeprecationType + +if TYPE_CHECKING: + from collections.abc import Callable + + from polars._utils.various import IdentityFunction + +if sys.version_info >= (3, 13): + from warnings import deprecated +else: + try: + from typing_extensions import deprecated + except ImportError: + + def deprecated(message: str) -> IdentityFunction: # type: ignore[no-redef] + return _deprecate_function(message) + + +from polars._warnings import issue_warning + +if TYPE_CHECKING: + from collections.abc import Mapping + from typing import ParamSpec + + from polars._typing import Ambiguous + + P = ParamSpec("P") + T = TypeVar("T") + +USE_EARLIEST_TO_AMBIGUOUS: Mapping[bool, Ambiguous] = { + True: "earliest", + False: "latest", +} + + +def issue_deprecation_warning(message: str, *, version: str = "") -> None: + """ + Issue a deprecation warning. + + Parameters + ---------- + message + The message associated with the warning. + version + The version in which deprecation occurred + (if the version number was not already included in `message`). + """ + if version: + message = f"{message.strip()}\n(Deprecated in version {version})" + issue_warning(message, DeprecationWarning) + + +def _deprecate_function(message: str) -> IdentityFunction: + """Decorator to mark a function as deprecated.""" + + def decorate(function: Callable[P, T]) -> Callable[P, T]: + @wraps(function) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + issue_deprecation_warning(message) + return function(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(function) # type: ignore[attr-defined] + wrapper.__deprecated__ = message # type: ignore[attr-defined] + return wrapper + + return decorate + + +def deprecate_renamed_parameter( + old_name: str, + new_name: str, + *, + version: str, +) -> IdentityFunction: + """ + Decorator to mark a function parameter as deprecated due to being renamed. + + Use as follows: + + @deprecate_renamed_parameter("old_name", new_name="new_name") + def myfunc(new_name): ... + + Ensure that you also update the function docstring with a note about the + deprecation, specifically adding a `.. versionchanged:: 0.0.0` directive + that states which parameter was renamed to which new name and in which + version the rename happened. + """ + + def decorate(function: Callable[P, T]) -> Callable[P, T]: + @wraps(function) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + _rename_keyword_argument( + old_name, new_name, kwargs, function.__qualname__, version + ) + return function(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(function) # type: ignore[attr-defined] + return wrapper + + return decorate + + +def _rename_keyword_argument( + old_name: str, + new_name: str, + kwargs: dict[str, object], + func_name: str, + version: str, +) -> None: + """Rename a keyword argument of a function.""" + if old_name in kwargs: + if new_name in kwargs: + is_deprecated = ( + f"was deprecated in version {version}" if version else "is deprecated" + ) + msg = ( + f"`{func_name!r}` received both `{old_name!r}` and `{new_name!r}` as arguments;" + f" `{old_name!r}` {is_deprecated}, use `{new_name!r}` instead" + ) + raise TypeError(msg) + + in_version = f" in version {version}" if version else "" + issue_deprecation_warning( + f"the argument `{old_name}` for `{func_name}` is deprecated. " + f"It was renamed to `{new_name}`{in_version}." + ) + kwargs[new_name] = kwargs.pop(old_name) + + +def deprecate_nonkeyword_arguments( + allowed_args: list[str] | None = None, message: str | None = None, *, version: str +) -> IdentityFunction: + """ + Decorator for deprecating the use of non-keyword arguments in a function. + + Use as follows: + + @deprecate_nonkeyword_arguments(allowed_args=["self", "val"], version="1.0.0") + def myfunc(self, val: int = 0, other: int: = 0): ... + + Ensure that you also update the function docstring with a note about the + deprecation, specifically adding a `.. versionchanged:: 0.0.0` directive + that states that we now expect keyword args and in which version this + update happened. + + Parameters + ---------- + allowed_args + The names of some first arguments of the decorated function that are allowed to + be given as positional arguments. Should include "self" when decorating class + methods. If set to None (default), equal to all arguments that do not have a + default value. + message + Optionally overwrite the default warning message. + version + The Polars version number in which the warning is first issued. + """ + + def decorate(function: Callable[P, T]) -> Callable[P, T]: + old_sig = inspect.signature(function) + + if allowed_args is not None: + allow_args = allowed_args + else: + allow_args = [ + p.name + for p in old_sig.parameters.values() + if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) + and p.default is p.empty + ] + + new_params = [ + p.replace(kind=p.KEYWORD_ONLY) + if ( + p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD) + and p.name not in allow_args + ) + else p + for p in old_sig.parameters.values() + ] + new_params.sort(key=lambda p: p.kind) + + new_sig = old_sig.replace(parameters=new_params) + + num_allowed_args = len(allow_args) + if message is None: + msg_format = ( + f"all arguments of {function.__qualname__}{{except_args}} will be keyword-only in the next breaking release." + " Use keyword arguments to silence this warning." + ) + msg = msg_format.format(except_args=_format_argument_list(allow_args)) + else: + msg = message + + @wraps(function) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + if len(args) > num_allowed_args: + issue_deprecation_warning(msg, version=version) + return function(*args, **kwargs) + + wrapper.__signature__ = new_sig # type: ignore[attr-defined] + return wrapper + + return decorate + + +def _format_argument_list(allowed_args: list[str]) -> str: + """Format allowed arguments list for use in the warning message of `deprecate_nonkeyword_arguments`.""" # noqa: W505 + if "self" in allowed_args: + allowed_args.remove("self") + if not allowed_args: + return "" + elif len(allowed_args) == 1: + return f" except for {allowed_args[0]!r}" + else: + last = allowed_args[-1] + args = ", ".join([f"{x!r}" for x in allowed_args[:-1]]) + return f" except for {args} and {last!r}" + + +def deprecate_parameter_as_multi_positional(old_name: str) -> IdentityFunction: + """ + Decorator to mark a function argument as deprecated due to being made multi-positional. + + Use as follows: + + @deprecate_parameter_as_multi_positional("columns") + def myfunc(*columns): ... + + Ensure that you also update the function docstring with a note about the + deprecation, specifically adding a `.. versionchanged:: 0.0.0` directive + that states that we now expect positional args and in which version this + update happened. + """ # noqa: W505 + + def decorate(function: Callable[P, T]) -> Callable[P, T]: + @wraps(function) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + try: + arg_value = kwargs.pop(old_name) + except KeyError: + return function(*args, **kwargs) + + issue_deprecation_warning( + f"passing `{old_name}` as a keyword argument is deprecated." + " Pass it as a positional argument instead." + ) + + if not isinstance(arg_value, Sequence) or isinstance(arg_value, str): + arg_value = (arg_value,) + elif not isinstance(arg_value, tuple): + arg_value = tuple(arg_value) + + args = args + arg_value # type: ignore[assignment] + return function(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(function) # type: ignore[attr-defined] + return wrapper + + return decorate + + +def _find_deprecated_functions( + source: str, module_path: str +) -> defaultdict[str, list[str]]: + tree = ast.parse(source) + object_path: list[str] = [] + + def deprecated(decorator: Any) -> str: + if isinstance(decorator, ast.Name): + return decorator.id if "deprecate" in decorator.id else "" + elif isinstance(decorator, ast.Call): + return deprecated(decorator.func) + return "" + + def qualified_name(func_name: str) -> str: + return ".".join([module_path, *object_path, func_name]) + + results = defaultdict(list) + + class FunctionVisitor(ast.NodeVisitor): + def visit_ClassDef(self, node: Any) -> None: + object_path.append(node.name) + self.generic_visit(node) + object_path.pop() + + def visit_FunctionDef(self, node: Any) -> None: + if any((decorator_name := deprecated(d)) for d in node.decorator_list): + key = decorator_name.removeprefix("deprecate_").replace( + "deprecated", "function" + ) + results[key].append(qualified_name(node.name)) + self.generic_visit(node) + + visit_AsyncFunctionDef = visit_FunctionDef + + FunctionVisitor().visit(tree) + return results + + +def identify_deprecations(*types: DeprecationType) -> dict[str, list[str]]: + """ + Return a dict identifying functions/methods that are deprecated in some way. + + Parameters + ---------- + *types + The types of deprecations to identify. + If empty, all types are returned; recognised values are: + - "function" + - "renamed_parameter" + - "nonkeyword_arguments" + - "parameter_as_multi_positional" + + Examples + -------- + >>> from polars._utils.deprecation import identify_deprecations + >>> identify_deprecations("nonkeyword_arguments") # doctest: +IGNORE_RESULT + {'nonkeyword_arguments': ['functions.business.business_day_count']} + """ + valid_types = set(get_args(DeprecationType)) + for tp in types: + if tp not in valid_types: + msg = ( + f"unrecognised deprecation type {tp!r}.\n" + f"Expected one (or more) of {repr(sorted(valid_types))[1:-1]}" + ) + raise ValueError(msg) + + package_path = Path(sys.modules["polars"].__file__).parent # type: ignore[arg-type] + results = defaultdict(list) + + for py_file in package_path.rglob("*.py"): + rel_path = py_file.relative_to(package_path) + module_path = ".".join(rel_path.parts).removesuffix(".py") + with py_file.open("r", encoding="utf-8") as src: + for deprecation_type, func_names in _find_deprecated_functions( + source=src.read(), + module_path=module_path, + ).items(): + if deprecation_type not in valid_types: + # note: raising here implies we have a new deprecation function + # that should be added to the DeprecationType type alias + msg = f"unrecognised deprecation type {tp!r}.\n" + raise ValueError(msg) + + results[deprecation_type].extend(func_names) + + return { + dep: sorted(results[dep]) + for dep in sorted(results) + if not types or dep in types + } + + +__all__ = [ + "deprecate_nonkeyword_arguments", + "deprecate_parameter_as_multi_positional", + "deprecate_renamed_parameter", + "deprecated", + "identify_deprecations", +] diff --git a/hug/lib/python3.12/site-packages/polars/_utils/expired.py b/hug/lib/python3.12/site-packages/polars/_utils/expired.py new file mode 100644 index 0000000000000000000000000000000000000000..242386301b7fc7766bf9716a7e6682a8cd94a6c6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/expired.py @@ -0,0 +1,161 @@ +from __future__ import annotations + +import inspect +from dataclasses import dataclass +from functools import wraps +from typing import TYPE_CHECKING, NoReturn, TypeVar + +from polars.exceptions import ArgumentRemovedError, AttributeRemovedError + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any, ParamSpec + + from polars._utils.various import IdentityFunction + + P = ParamSpec("P") + T = TypeVar("T") + + +@dataclass(frozen=True, kw_only=True) +class RemovedParameter: + name: str + deprecated_in: str | None = None + removed_in: str + hint: str | None = None + + +@dataclass(frozen=True, kw_only=True) +class RenamedParameter: + name: str + new_name: str + deprecated_in: str | None = None + removed_in: str + hint: str | None = None + + +def raise_for_removed_attributes( + obj: object, + name: str, + attributes: dict[str, str | None], + *, + version: str, +) -> None: + """ + Raise an `AttributeError` for a removed attribute. + + Parameters + ---------- + obj + The object from which the attribute was removed. + name + The name of the removed attribute. + attributes + A dictionary mapping removed attribute names to hints for replacement. + version + The version in which the attribute was removed. + """ + if name in attributes: + hint = attributes[name] + msg = f"`{name}` was removed in version {version}" + msg = f"{msg}." if hint is None else f"{msg}; {hint}" + raise AttributeRemovedError(msg, name=name, obj=obj) + + +def getattr_fallback( + obj: object, superclass: object, name: str, *, meta: bool = False +) -> Any: + """ + Raise an `AttributeError` for a non-existent attribute. + + Parameters + ---------- + obj + The object on which the attribute was accessed. + superclass + The superclass of the object used to attempt to access the attribute. + name + The name of the non-existent attribute. + meta + Whether the object is a metaclass (default: False). + """ + if (super_getattr := getattr(superclass, "__getattr__", None)) is not None: + return super_getattr(name) + else: + objname = f"{obj.__name__!r}" if meta else f"{type(obj).__name__!r}" # type: ignore[attr-defined] + msg = f"{objname} object has no attribute {name!r}" + raise AttributeError(msg, name=name, obj=obj) + + +def removed_parameters( + *params: RemovedParameter | RenamedParameter, +) -> IdentityFunction: + """ + Decorator to mark function parameters. + + This decorator expects a number of `RemovedParameter` or `RenamedParameter` + instances that describe each of the removed parameters of the method. + """ + assert len(params) == len({p.name for p in params}), ( + "duplicate parameter in removed parameter list" + ) + params_dict = {p.name: p for p in params} + + def decorate(function: Callable[P, T]) -> Callable[P, T]: + @wraps(function) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + for name in kwargs: + if name in params_dict: + _raise_removed_argument_error( + params_dict[name], + func_name=function.__qualname__, + kwargs=kwargs, + ) + return function(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(function) # type: ignore[attr-defined] + # Stash the removed parameters on the wrapper so that they can be introspected + # later, in case the function needs to go through @expr_dispatch later. + wrapper.__removed_parameters__ = params # type: ignore[attr-defined] + return wrapper + + return decorate + + +def _raise_removed_argument_error( + param: RemovedParameter | RenamedParameter, + *, + func_name: str, + kwargs: dict[str, object], +) -> NoReturn: + was_deprecated_and = ( + f" was deprecated in version {param.deprecated_in} and" + if param.deprecated_in is not None + else "" + ) + if isinstance(param, RenamedParameter): + if param.name in kwargs and param.new_name in kwargs: + msg = ( + f"{func_name!r} received both {param.name!r} and {param.new_name!r} as arguments;" + f" {param.name!r}{was_deprecated_and} has been renamed to" + f" {param.new_name!r} in version {param.removed_in}." + ) + raise ArgumentRemovedError(msg) + else: + msg = ( + f"the argument {param.name!r} for {func_name!r}{was_deprecated_and}" + f" has been removed in version {param.removed_in}." + f" It was renamed to {param.new_name!r}." + ) + msg = msg if param.hint is None else f"{msg} {param.hint}" + raise ArgumentRemovedError(msg) + elif isinstance(param, RemovedParameter): + msg = ( + f"the argument {param.name!r} for {func_name!r}{was_deprecated_and}" + f" has been removed in version {param.removed_in}." + ) + msg = msg if param.hint is None else f"{msg} {param.hint}" + raise ArgumentRemovedError(msg) + else: + msg = f"Unexpected parameter type: {type(param)!r}" + raise TypeError(msg) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/getitem.py b/hug/lib/python3.12/site-packages/polars/_utils/getitem.py new file mode 100644 index 0000000000000000000000000000000000000000..d5a166ab01dfa1ad478c0f38233d7a455bdbc422 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/getitem.py @@ -0,0 +1,457 @@ +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, NoReturn, overload + +import polars._reexport as pl +import polars.functions as F +from polars._dependencies import _check_for_numpy +from polars._dependencies import numpy as np +from polars._utils.constants import U32_MAX +from polars._utils.slice import PolarsSlice +from polars._utils.various import qualified_type_name, range_to_slice +from polars.datatypes.classes import ( + Boolean, + Int8, + Int16, + Int32, + Int64, + String, + UInt32, + UInt64, +) +from polars.meta.index_type import get_index_type + +if TYPE_CHECKING: + from collections.abc import Iterable + + from polars import DataFrame, Series + from polars._typing import ( + MultiColSelector, + MultiIndexSelector, + SingleColSelector, + SingleIndexSelector, + ) + +__all__ = [ + "get_df_item_by_key", + "get_series_item_by_key", +] + + +@overload +def get_series_item_by_key(s: Series, key: SingleIndexSelector) -> Any: ... + + +@overload +def get_series_item_by_key(s: Series, key: MultiIndexSelector) -> Series: ... + + +def get_series_item_by_key( + s: Series, key: SingleIndexSelector | MultiIndexSelector +) -> Any | Series: + """Select one or more elements from the Series.""" + if isinstance(key, int): + return s._s.get_index_signed(key) + + elif isinstance(key, slice): + return _select_elements_by_slice(s, key) + + elif isinstance(key, range): + key = range_to_slice(key) + return _select_elements_by_slice(s, key) + + elif isinstance(key, Sequence): + if not key: + return s.clear() + + first = key[0] + if isinstance(first, bool): + _raise_on_boolean_mask() + + try: + indices = pl.Series("", key, dtype=Int64) + except TypeError: + msg = f"cannot select elements using Sequence with elements of type {qualified_type_name(first)!r}" + raise TypeError(msg) from None + + indices = _convert_series_to_indices(indices, s.len()) + return _select_elements_by_index(s, indices) + + elif isinstance(key, pl.Series): + indices = _convert_series_to_indices(key, s.len()) + return _select_elements_by_index(s, indices) + + elif _check_for_numpy(key) and isinstance(key, np.ndarray): + indices = _convert_np_ndarray_to_indices(key, s.len()) + return _select_elements_by_index(s, indices) + + msg = f"cannot select elements using key of type {qualified_type_name(key)!r}: {key!r}" + raise TypeError(msg) + + +def _select_elements_by_slice(s: Series, key: slice) -> Series: + return PolarsSlice(s).apply(key) # type: ignore[return-value] + + +def _select_elements_by_index(s: Series, key: Series) -> Series: + return s._from_pyseries(s._s.gather_with_series(key._s)) + + +# `str` overlaps with `Sequence[str]` +# We can ignore this but we must keep this overload ordering +@overload +def get_df_item_by_key( + df: DataFrame, key: tuple[SingleIndexSelector, SingleColSelector] +) -> Any: ... + + +@overload +def get_df_item_by_key( # type: ignore[overload-overlap] + df: DataFrame, key: str | tuple[MultiIndexSelector, SingleColSelector] +) -> Series: ... + + +@overload +def get_df_item_by_key( + df: DataFrame, + key: ( + SingleIndexSelector + | MultiIndexSelector + | MultiColSelector + | tuple[SingleIndexSelector, MultiColSelector] + | tuple[MultiIndexSelector, MultiColSelector] + ), +) -> DataFrame: ... + + +def get_df_item_by_key( + df: DataFrame, + key: ( + SingleIndexSelector + | SingleColSelector + | MultiColSelector + | MultiIndexSelector + | tuple[SingleIndexSelector, SingleColSelector] + | tuple[SingleIndexSelector, MultiColSelector] + | tuple[MultiIndexSelector, SingleColSelector] + | tuple[MultiIndexSelector, MultiColSelector] + ), +) -> DataFrame | Series | Any: + """Get part of the DataFrame as a new DataFrame, Series, or scalar.""" + # Two inputs, e.g. df[1, 2:5] + if isinstance(key, tuple) and len(key) == 2: + row_key, col_key = key + + # Support df[True, False] and df["a", "b"] as these are not ambiguous + if isinstance(row_key, (bool, str)): + return _select_columns(df, key) # type: ignore[arg-type] + + selection = _select_columns(df, col_key) + + if selection.is_empty(): + return selection + elif isinstance(selection, pl.Series): + return get_series_item_by_key(selection, row_key) + else: + return _select_rows(selection, row_key) + + # Single string input, e.g. df["a"] + if isinstance(key, str): + # This case is required because empty strings are otherwise treated + # as an empty Sequence in `_select_rows` + return df.get_column(key) + + # Single input - df[1] - or multiple inputs - df["a", "b", "c"] + try: + return _select_rows(df, key) # type: ignore[arg-type] + except TypeError: + return _select_columns(df, key) + + +# `str` overlaps with `Sequence[str]` +# We can ignore this but we must keep this overload ordering +@overload +def _select_columns(df: DataFrame, key: SingleColSelector) -> Series: ... # type: ignore[overload-overlap] + + +@overload +def _select_columns(df: DataFrame, key: MultiColSelector) -> DataFrame: ... + + +def _select_columns( + df: DataFrame, key: SingleColSelector | MultiColSelector +) -> DataFrame | Series: + """Select one or more columns from the DataFrame.""" + if isinstance(key, int): + return df.to_series(key) + + elif isinstance(key, str): + return df.get_column(key) + + elif isinstance(key, slice): + start, stop, step = key.start, key.stop, key.step + # Fast path for common case: df[x, :] + if start is None and stop is None and step is None: + return df + if isinstance(start, str): + start = df.get_column_index(start) + if isinstance(stop, str): + stop = df.get_column_index(stop) + 1 + int_slice = slice(start, stop, step) + rng = range(df.width)[int_slice] + return _select_columns_by_index(df, rng) + + elif isinstance(key, range): + return _select_columns_by_index(df, key) + + elif isinstance(key, Sequence): + if not key: + return df.__class__() + first = key[0] + if isinstance(first, bool): + return _select_columns_by_mask(df, key) # type: ignore[arg-type] + elif isinstance(first, int): + return _select_columns_by_index(df, key) # type: ignore[arg-type] + elif isinstance(first, str): + return _select_columns_by_name(df, key) # type: ignore[arg-type] + else: + msg = f"cannot select columns using Sequence with elements of type {qualified_type_name(first)!r}" + raise TypeError(msg) + + elif isinstance(key, pl.Series): + if key.is_empty(): + return df.__class__() + dtype = key.dtype + if dtype == String: + return _select_columns_by_name(df, key) + elif dtype.is_integer(): + return _select_columns_by_index(df, key) + elif dtype == Boolean: + return _select_columns_by_mask(df, key) + else: + msg = f"cannot select columns using Series of type {dtype}" + raise TypeError(msg) + + elif _check_for_numpy(key) and isinstance(key, np.ndarray): + if key.ndim == 0: + key = np.atleast_1d(key) + elif key.ndim != 1: + msg = "multi-dimensional NumPy arrays not supported as index" + raise TypeError(msg) + + if len(key) == 0: + return df.__class__() + + dtype_kind = key.dtype.kind + if dtype_kind in ("i", "u"): + return _select_columns_by_index(df, key) + elif dtype_kind == "b": + return _select_columns_by_mask(df, key) + elif isinstance(key[0], str): + return _select_columns_by_name(df, key) + else: + msg = f"cannot select columns using NumPy array of type {key.dtype}" + raise TypeError(msg) + + msg = ( + f"cannot select columns using key of type {qualified_type_name(key)!r}: {key!r}" + ) + raise TypeError(msg) + + +def _select_columns_by_index(df: DataFrame, key: Iterable[int]) -> DataFrame: + series = [df.to_series(i) for i in key] + return df.__class__(series) + + +def _select_columns_by_name(df: DataFrame, key: Iterable[str]) -> DataFrame: + return df._from_pydf(df._df.select(list(key))) + + +def _select_columns_by_mask( + df: DataFrame, key: Sequence[bool] | Series | np.ndarray[Any, Any] +) -> DataFrame: + if len(key) != df.width: + msg = f"expected {df.width} values when selecting columns by boolean mask, got {len(key)}" + raise ValueError(msg) + + indices = (i for i, val in enumerate(key) if val) + return _select_columns_by_index(df, indices) + + +@overload +def _select_rows(df: DataFrame, key: SingleIndexSelector) -> Series: ... + + +@overload +def _select_rows(df: DataFrame, key: MultiIndexSelector) -> DataFrame: ... + + +def _select_rows( + df: DataFrame, key: SingleIndexSelector | MultiIndexSelector +) -> DataFrame | Series: + """Select one or more rows from the DataFrame.""" + if isinstance(key, int): + num_rows = df.height + if (key >= num_rows) or (key < -num_rows): + msg = f"index {key} is out of bounds for DataFrame of height {num_rows}" + raise IndexError(msg) + return df.slice(key, 1) + + if isinstance(key, slice): + return _select_rows_by_slice(df, key) + + elif isinstance(key, range): + key = range_to_slice(key) + return _select_rows_by_slice(df, key) + + elif isinstance(key, Sequence): + if not key: + return df.clear() + if isinstance(key[0], bool): + _raise_on_boolean_mask() + s = pl.Series("", key, dtype=Int64) + indices = _convert_series_to_indices(s, df.height) + return _select_rows_by_index(df, indices) + + elif isinstance(key, pl.Series): + indices = _convert_series_to_indices(key, df.height) + return _select_rows_by_index(df, indices) + + elif _check_for_numpy(key) and isinstance(key, np.ndarray): + indices = _convert_np_ndarray_to_indices(key, df.height) + return _select_rows_by_index(df, indices) + + else: + msg = f"cannot select rows using key of type {qualified_type_name(key)!r}: {key!r}" + raise TypeError(msg) + + +def _select_rows_by_slice(df: DataFrame, key: slice) -> DataFrame: + return PolarsSlice(df).apply(key) # type: ignore[return-value] + + +def _select_rows_by_index(df: DataFrame, key: Series) -> DataFrame: + return df._from_pydf(df._df.gather_with_series(key._s)) + + +# UTILS + + +def _convert_series_to_indices(s: Series, size: int) -> Series: + """Convert a Series to indices, taking into account negative values.""" + # Unsigned or signed Series (ordered from fastest to slowest). + # - pl.UInt32 (polars) or pl.UInt64 (polars_u64_idx) Series indexes. + # - Other unsigned Series indexes are converted to pl.UInt32 (polars) + # or pl.UInt64 (polars_u64_idx). + # - Signed Series indexes are converted pl.UInt32 (polars) or + # pl.UInt64 (polars_u64_idx) after negative indexes are converted + # to absolute indexes. + + # pl.UInt32 (polars) or pl.UInt64 (polars_u64_idx). + idx_type = get_index_type() + + if s.dtype == idx_type: + return s + + if not s.dtype.is_integer(): + if s.dtype == Boolean: + _raise_on_boolean_mask() + else: + msg = f"cannot treat Series of type {s.dtype} as indices" + raise TypeError(msg) + + if s.len() == 0: + return pl.Series(s.name, [], dtype=idx_type) + + if idx_type == UInt32: + if s.dtype in {Int64, UInt64} and s.max() >= U32_MAX: # type: ignore[operator] + msg = "index positions should be smaller than 2^32" + raise ValueError(msg) + if s.dtype == Int64 and s.min() < -U32_MAX: # type: ignore[operator] + msg = "index positions should be greater than or equal to -2^32" + raise ValueError(msg) + + if s.dtype.is_signed_integer(): + if s.min() < 0: # type: ignore[operator] + if idx_type == UInt32: + idxs = s.cast(Int32) if s.dtype in {Int8, Int16} else s + else: + idxs = s.cast(Int64) if s.dtype in {Int8, Int16, Int32} else s + + # Update negative indexes to absolute indexes. + return ( + idxs.to_frame() + .select( + F.when(F.col(idxs.name) < 0) + .then(size + F.col(idxs.name)) + .otherwise(F.col(idxs.name)) + .cast(idx_type) + ) + .to_series(0) + ) + + return s.cast(idx_type) + + +def _convert_np_ndarray_to_indices(arr: np.ndarray[Any, Any], size: int) -> Series: + """Convert a NumPy ndarray to indices, taking into account negative values.""" + # Unsigned or signed Numpy array (ordered from fastest to slowest). + # - np.uint32 (polars) or np.uint64 (polars_u64_idx) numpy array + # indexes. + # - Other unsigned numpy array indexes are converted to pl.UInt32 + # (polars) or pl.UInt64 (polars_u64_idx). + # - Signed numpy array indexes are converted pl.UInt32 (polars) or + # pl.UInt64 (polars_u64_idx) after negative indexes are converted + # to absolute indexes. + if arr.ndim == 0: + arr = np.atleast_1d(arr) + if arr.ndim != 1: + msg = "only 1D NumPy arrays can be treated as indices" + raise TypeError(msg) + + idx_type = get_index_type() + + if len(arr) == 0: + return pl.Series("", [], dtype=idx_type) + + # Numpy array with signed or unsigned integers. + if arr.dtype.kind not in ("i", "u"): + if arr.dtype.kind == "b": + _raise_on_boolean_mask() + else: + msg = f"cannot treat NumPy array of type {arr.dtype} as indices" + raise TypeError(msg) + + if idx_type == UInt32: + if arr.dtype in {np.int64, np.uint64} and arr.max() >= U32_MAX: + msg = "index positions should be smaller than 2^32" + raise ValueError(msg) + if arr.dtype == np.int64 and arr.min() < -U32_MAX: + msg = "index positions should be greater than or equal to -2^32" + raise ValueError(msg) + + if arr.dtype.kind == "i" and arr.min() < 0: + if idx_type == UInt32: + if arr.dtype in (np.int8, np.int16): + arr = arr.astype(np.int32) + else: + if arr.dtype in (np.int8, np.int16, np.int32): + arr = arr.astype(np.int64) + + # Update negative indexes to absolute indexes. + arr = np.where(arr < 0, size + arr, arr) + + # numpy conversion is much faster + arr = arr.astype(np.uint32) if idx_type == UInt32 else arr.astype(np.uint64) + + return pl.Series("", arr, dtype=idx_type) + + +def _raise_on_boolean_mask() -> NoReturn: + msg = ( + "selecting rows by passing a boolean mask to `__getitem__` is not supported" + "\n\nHint: Use the `filter` method instead." + ) + raise TypeError(msg) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/logging.py b/hug/lib/python3.12/site-packages/polars/_utils/logging.py new file mode 100644 index 0000000000000000000000000000000000000000..50a25f466afed09df0dbf3af419c27edcc2868a9 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/logging.py @@ -0,0 +1,19 @@ +import os +import sys +from collections.abc import Callable +from typing import Any + + +def verbose() -> bool: + return os.getenv("POLARS_VERBOSE") == "1" + + +def eprint(*a: Any, **kw: Any) -> None: + return print(*a, file=sys.stderr, **kw) + + +def verbose_print_sensitive(create_log_message: Callable[[], str]) -> None: + if os.getenv("POLARS_VERBOSE_SENSITIVE") == "1": + # Force the message to be a single line. + msg = create_log_message().replace("\n", "") + print(f"[SENSITIVE]: {msg}", file=sys.stderr) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/monitoring.py b/hug/lib/python3.12/site-packages/polars/_utils/monitoring.py new file mode 100644 index 0000000000000000000000000000000000000000..92990aee7a34029538ace3f8e433c606b7ca9f1c --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/monitoring.py @@ -0,0 +1,37 @@ +"""Shared utilities for query monitoring.""" + +from __future__ import annotations + +import os +from typing import Final + +MONITORING_ENV_VAR: Final[str] = "POLARS_QUERY_MONITORING" +MONITORING_WORKSPACE_ENV_VAR: Final[str] = "POLARS_QUERY_MONITORING_WORKSPACE" +MONITORING_ORGANIZATION_ENV_VAR: Final[str] = "POLARS_QUERY_MONITORING_ORGANIZATION" + + +def monitoring_enabled_globally() -> bool: + return os.environ.get(MONITORING_ENV_VAR) == "1" + + +def monitoring_workspace() -> str | None: + """The Polars Cloud workspace metrics are sent to, `None` for the default one.""" + return os.environ.get(MONITORING_WORKSPACE_ENV_VAR) or None + + +def monitoring_organization() -> str | None: + """The Polars Cloud organization metrics are sent to, `None` for the default one.""" + return os.environ.get(MONITORING_ORGANIZATION_ENV_VAR) or None + + +def activate_monitoring() -> None: + """Ensure `polars_cloud` is installed and this session is authenticated.""" + from polars._dependencies import import_optional, polars_cloud + + import_optional( + polars_cloud.__name__, + err_prefix="query monitoring requires the", + install_message=( + "Please install using the command `pip install 'polars-cloud>=0.13.0'`." + ), + ).authenticate() diff --git a/hug/lib/python3.12/site-packages/polars/_utils/nest_asyncio.py b/hug/lib/python3.12/site-packages/polars/_utils/nest_asyncio.py new file mode 100644 index 0000000000000000000000000000000000000000..c0ee2e3fc43e0148c7baeae1a8cfa48d846a81f5 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/nest_asyncio.py @@ -0,0 +1,430 @@ +# +# Originally vendored from https://github.com/Chaoses-Ib/nest-asyncio2 +# + +# BSD 2-Clause License + +# Copyright (c) 2025 Ritchie Vink +# Copyright (c) 2018-2020, Ewald de Wit +# 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. + +# 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. + +# Ignore all lints, file is currently copied. +# ruff: noqa +# type: ignore + +"""Patch asyncio to allow nested event loops.""" + +import asyncio +import asyncio.events as events +import os +import sys +import threading +from collections import deque +from contextlib import contextmanager, suppress +from heapq import heappop + +_run_close_loop = True + + +class _CancelledHandle: + """Placeholder for a ready callback that a nested loop run already executed.""" + + _cancelled = True + + def _run(self): + pass + + +class _ReentrantReady(deque): + """A queue that returns a dummy cancelled handle on empty pop rather than erroring.""" + + def popleft(self): + try: + return super().popleft() + except IndexError: + return _CancelledHandle() + + +class _NestAsyncio2: + """Internal class of `nest_asyncio2`. + + Mainly for holding the original properties to support unapply() and nest_asyncio2.run(). + """ + + pass + + +def apply( + loop=None, *, run_close_loop: bool = False, error_on_mispatched: bool = False +): + """Patch asyncio to make its event loop reentrant. + + - `run_close_loop`: Close the event loop created by `asyncio.run()`, if any. + See README for details. + - `error_on_mispatched`: + - `False` (default): Warn if asyncio is already patched by `nest_asyncio` on Python 3.12+. + - `True`: Raise `RuntimeError` if asyncio is already patched by `nest_asyncio`. + """ + global _run_close_loop + + _patch_asyncio(error_on_mispatched=error_on_mispatched) + _patch_policy() + _patch_tornado() + + loop = loop or _get_event_loop() + if loop is not None: + _patch_loop(loop) + + _run_close_loop &= run_close_loop + + +if sys.version_info < (3, 12, 0): + + def _get_event_loop(): + return asyncio.get_event_loop() +elif sys.version_info < (3, 14, 0): + + def _get_event_loop(): + # Python 3.12~3.13: + # Calling get_event_loop() will result in ResourceWarning: unclosed event loop + loop = events._get_running_loop() + if loop is None: + policy = events.get_event_loop_policy() + loop = policy._local._loop + return loop +else: + + def _get_event_loop(): + # Python 3.14: Raises a RuntimeError if there is no current event loop. + try: + return asyncio.get_event_loop() + except RuntimeError: + return None + + +if sys.version_info < (3, 12, 0): + + def run(main, *, debug=False): + loop = asyncio.get_event_loop() + loop.set_debug(debug) + task = asyncio.ensure_future(main) + try: + return loop.run_until_complete(task) + finally: + if not task.done(): + task.cancel() + with suppress(asyncio.CancelledError): + loop.run_until_complete(task) +else: + + def run(main, *, debug=False, loop_factory=None): + new_event_loop = False + set_event_loop = None + try: + loop = asyncio.get_running_loop() + except RuntimeError: + # if sys.version_info < (3, 16, 0): + # policy = asyncio.events._get_event_loop_policy() + # try: + # loop = policy.get_event_loop() + # except RuntimeError: + # loop = loop_factory() + # else: + # loop = loop_factory() + if not _run_close_loop: + # Not running + loop = _get_event_loop() + if loop is None: + if loop_factory is None: + loop_factory = asyncio.new_event_loop + loop = loop_factory() + asyncio.set_event_loop(loop) + else: + if loop_factory is None: + loop = asyncio.new_event_loop() + # Not running + set_event_loop = _get_event_loop() + asyncio.set_event_loop(loop) + else: + loop = loop_factory() + new_event_loop = True + _patch_loop(loop) + + loop.set_debug(debug) + task = asyncio.ensure_future(main, loop=loop) + try: + return loop.run_until_complete(task) + finally: + if not task.done(): + task.cancel() + with suppress(asyncio.CancelledError): + loop.run_until_complete(task) + if set_event_loop: + # asyncio.Runner just set_event_loop(None) but we are nested + asyncio.set_event_loop(set_event_loop) + if new_event_loop: + # Avoid ResourceWarning: unclosed event loop + loop.close() + + +def _patch_asyncio(*, error_on_mispatched: bool = False): + """Patch asyncio module to use pure Python tasks and futures.""" + + def _get_event_loop(stacklevel=3): + loop = events._get_running_loop() + if loop is None: + loop = events.get_event_loop_policy().get_event_loop() + return loop + + # Use module level _current_tasks, all_tasks and patch run method. + if hasattr(asyncio, "_nest_patched"): + if not hasattr(asyncio, "_nest_asyncio2"): + if error_on_mispatched: + raise RuntimeError("asyncio is already patched by nest_asyncio") + elif sys.version_info >= (3, 12, 0): + import warnings + + warnings.warn( + "asyncio is already patched by nest_asyncio. You may encounter bugs related to asyncio" + ) + return + + # Using _PyTask on Python 3.14+ will break current_task() (and all_tasks(), + # _swap_current_task()) + # Even we replace it with _py_current_task(), it only works with _PyTask, but + # the external loop is probably using _CTask. + # https://github.com/python/cpython/pull/129899 + if sys.version_info >= (3, 6, 0) and sys.version_info < (3, 14, 0): + asyncio.Task = asyncio.tasks._CTask = asyncio.tasks.Task = asyncio.tasks._PyTask + asyncio.Future = asyncio.futures._CFuture = asyncio.futures.Future = ( + asyncio.futures._PyFuture + ) + if sys.version_info < (3, 7, 0): + asyncio.tasks._current_tasks = asyncio.tasks.Task._current_tasks + asyncio.all_tasks = asyncio.tasks.Task.all_tasks + # The same as asyncio.get_event_loop() on at least Python 3.14 + if sys.version_info >= (3, 9, 0) and sys.version_info < (3, 14, 0): + events._get_event_loop = events.get_event_loop = asyncio.get_event_loop = ( + _get_event_loop + ) + asyncio.run = run + asyncio._nest_patched = True + asyncio._nest_asyncio2 = _NestAsyncio2() + + +def _patch_policy(): + """Patch the policy to always return a patched loop.""" + + # Python 3.14: + # get_event_loop() raises a RuntimeError if there is no current event loop. + # So there is no need to _patch_loop() in it. + # Patching new_event_loop() may be better, but policy is going to be removed... + # Removed in Python 3.16 + # https://github.com/python/cpython/issues/127949 + if sys.version_info >= (3, 14, 0): + return + + def get_event_loop(self): + if self._local._loop is None: + loop = self.new_event_loop() + _patch_loop(loop) + self.set_event_loop(loop) + return self._local._loop + + if sys.version_info < (3, 14, 0): + policy = events.get_event_loop_policy() + else: + policy = events._get_event_loop_policy() + policy.__class__.get_event_loop = get_event_loop + + +def _patch_loop(loop): + """Patch loop to make it reentrant.""" + + def run_forever(self): + with manage_run(self), manage_asyncgens(self): + while True: + self._run_once() + if self._stopping: + break + self._stopping = False + + def run_until_complete(self, future): + with manage_run(self): + f = asyncio.ensure_future(future, loop=self) + if f is not future: + f._log_destroy_pending = False + while not f.done(): + self._run_once() + if self._stopping: + break + if not f.done(): + raise RuntimeError("Event loop stopped before Future completed.") + return f.result() + + def _run_once(self): + """ + Simplified re-implementation of asyncio's _run_once that + runs handles as they become ready. + """ + scheduled = self._scheduled + while scheduled and scheduled[0]._cancelled: + heappop(scheduled) + + timeout = ( + 0 + if self._ready or self._stopping + else min(max(scheduled[0]._when - self.time(), 0), 86400) + if scheduled + else None + ) + event_list = self._selector.select(timeout) + self._process_events(event_list) + + end_time = self.time() + self._clock_resolution + while scheduled and scheduled[0]._when < end_time: + handle = heappop(scheduled) + self._ready.append(handle) + + for _ in range(len(self._ready)): + if not self._ready: + break + handle = self._ready.popleft() + if not handle._cancelled: + # preempt the current task so that that checks in + # Task.__step do not raise + if sys.version_info < (3, 14, 0): + curr_task = curr_tasks.pop(self, None) + else: + # Work with both C and Py + try: + curr_task = asyncio.tasks._swap_current_task(self, None) + except KeyError: + curr_task = None + + try: + handle._run() + finally: + # restore the current task + if curr_task is not None: + if sys.version_info < (3, 14, 0): + curr_tasks[self] = curr_task + else: + # Work with both C and Py + asyncio.tasks._swap_current_task(self, curr_task) + + handle = None + + @contextmanager + def manage_run(self): + """Set up the loop for running.""" + self._check_closed() + old_thread_id = self._thread_id + old_running_loop = events._get_running_loop() + try: + self._thread_id = threading.get_ident() + events._set_running_loop(self) + self._num_runs_pending += 1 + if self._is_proactorloop: + if self._self_reading_future is None: + self.call_soon(self._loop_self_reading) + yield + finally: + self._thread_id = old_thread_id + events._set_running_loop(old_running_loop) + self._num_runs_pending -= 1 + if self._is_proactorloop: + if ( + self._num_runs_pending == 0 + and self._self_reading_future is not None + ): + ov = self._self_reading_future._ov + self._self_reading_future.cancel() + if ov is not None: + self._proactor._unregister(ov) + self._self_reading_future = None + + @contextmanager + def manage_asyncgens(self): + if not hasattr(sys, "get_asyncgen_hooks"): + # Python version is too old. + return + old_agen_hooks = sys.get_asyncgen_hooks() + try: + self._set_coroutine_origin_tracking(self._debug) + if self._asyncgens is not None: + sys.set_asyncgen_hooks( + firstiter=self._asyncgen_firstiter_hook, + finalizer=self._asyncgen_finalizer_hook, + ) + yield + finally: + self._set_coroutine_origin_tracking(False) + if self._asyncgens is not None: + sys.set_asyncgen_hooks(*old_agen_hooks) + + def _check_running(self): + """Do not throw exception if loop is already running.""" + pass + + if not isinstance(loop, asyncio.BaseEventLoop): + raise ValueError("Can't patch loop of type %s" % type(loop)) + if not isinstance(loop._ready, _ReentrantReady): + old_ready, loop._ready = loop._ready, _ReentrantReady() + while old_ready: + loop._ready.append(old_ready.popleft()) + if hasattr(loop, "_nest_patched"): + return + cls = loop.__class__ + cls.run_forever = run_forever + cls.run_until_complete = run_until_complete + cls._run_once = _run_once + cls._check_running = _check_running + cls._check_runnung = _check_running # typo in Python 3.7 source + cls._num_runs_pending = 1 if loop.is_running() else 0 + cls._is_proactorloop = os.name == "nt" and issubclass( + cls, asyncio.ProactorEventLoop + ) + if sys.version_info < (3, 7, 0): + cls._set_coroutine_origin_tracking = cls._set_coroutine_wrapper + curr_tasks = ( + asyncio.tasks._current_tasks + if sys.version_info >= (3, 7, 0) + else asyncio.Task._current_tasks + ) + cls._nest_patched = True + cls._nest_asyncio2 = _NestAsyncio2() + + +def _patch_tornado(): + """ + If tornado is imported before nest_asyncio, make tornado aware of + the pure-Python asyncio Future. + """ + if "tornado" in sys.modules: + import tornado.concurrent as tc # type: ignore + + tc.Future = asyncio.Future + if asyncio.Future not in tc.FUTURES: + tc.FUTURES += (asyncio.Future,) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/parquet.py b/hug/lib/python3.12/site-packages/polars/_utils/parquet.py new file mode 100644 index 0000000000000000000000000000000000000000..41a3a4933a6376eecf765e506ce1a52bf2c4560e --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/parquet.py @@ -0,0 +1,16 @@ +from collections.abc import Callable +from typing import Any + +from polars._typing import ParquetMetadataContext, ParquetMetadataFn + + +def wrap_parquet_metadata_callback( + fn: ParquetMetadataFn, +) -> Callable[[Any], list[tuple[str, str]]]: + def pyo3_compatible_callback(ctx: Any) -> list[tuple[str, str]]: + ctx_py = ParquetMetadataContext( + arrow_schema=ctx.arrow_schema, + ) + return list(fn(ctx_py).items()) + + return pyo3_compatible_callback diff --git a/hug/lib/python3.12/site-packages/polars/_utils/parse/__init__.py b/hug/lib/python3.12/site-packages/polars/_utils/parse/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c062e3c71aa51c80d671e61c67ffeda60a48ff81 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/parse/__init__.py @@ -0,0 +1,14 @@ +from polars._utils.parse.expr import ( + parse_into_expression, + parse_into_list_of_expressions, + parse_into_list_of_expressions_require_selectors, + parse_predicates_constraints_into_expression, +) + +__all__ = [ + # expr + "parse_into_expression", + "parse_into_list_of_expressions", + "parse_into_list_of_expressions_require_selectors", + "parse_predicates_constraints_into_expression", +] diff --git a/hug/lib/python3.12/site-packages/polars/_utils/parse/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/parse/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c38d64eb604cd0e3651ac1ada54873eeb5f3a737 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/parse/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/parse/__pycache__/expr.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/_utils/parse/__pycache__/expr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f15d0ff10627db5d383ee9ba081b082b94dd5d3 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/_utils/parse/__pycache__/expr.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/_utils/parse/expr.py b/hug/lib/python3.12/site-packages/polars/_utils/parse/expr.py new file mode 100644 index 0000000000000000000000000000000000000000..22c5ec6737d40dfc8eff4ee3819d24e9b267eb11 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/parse/expr.py @@ -0,0 +1,305 @@ +from __future__ import annotations + +import contextlib +from collections.abc import Collection, Iterable, Mapping +from typing import TYPE_CHECKING, Any, Literal, overload + +import polars._reexport as pl +from polars import functions as F +from polars._utils.various import qualified_type_name +from polars.exceptions import ComputeError + +with contextlib.suppress(ImportError): # Module not available when building docs + import polars._plr as plr + +if TYPE_CHECKING: + from polars import Expr + from polars._plr import PyExpr + from polars._typing import ColumnNameOrSelector, IntoExpr, PolarsDataType + + +def parse_into_expression( + input: IntoExpr, + *, + str_as_lit: bool = False, + list_as_series: bool = False, + structify: bool = False, + dtype: PolarsDataType | None = None, + require_selector: bool = False, +) -> PyExpr: + """ + Parse a single input into an expression. + + Parameters + ---------- + input + The input to be parsed as an expression. + str_as_lit + Interpret string input as a string literal. If set to `False` (default), + strings are parsed as column names. + list_as_series + Interpret list input as a Series literal. If set to `False` (default), + lists are parsed as list literals. + structify + Convert multi-column expressions to a single struct expression. + dtype + If the input is expected to resolve to a literal with a known dtype, pass + this to the `lit` constructor. + require_selector + Require that the input is a valid selector (eg: column name or selector). + + Returns + ------- + PyExpr + """ + if isinstance(input, pl.Expr): + expr = input + if structify: + expr = _structify_expression(expr) + elif isinstance(input, str) and not str_as_lit: + expr = F.col(input) + else: + if require_selector: + msg = f"cannot turn {qualified_type_name(input)!r} into selector" + raise TypeError(msg) + elif isinstance(input, list) and list_as_series: + expr = F.lit(pl.Series(input), dtype=dtype) + else: + expr = F.lit(input, dtype=dtype) + + return expr._pyexpr + + +def _structify_expression(expr: Expr) -> Expr: + unaliased_expr = expr.meta.undo_aliases() + if unaliased_expr.meta.has_multiple_outputs(): + try: + expr_name = expr.meta.output_name() + except ComputeError: + expr = F.struct(expr) + else: + expr = F.struct(unaliased_expr).alias(expr_name) + return expr + + +def parse_into_list_of_expressions( + *inputs: IntoExpr | Iterable[IntoExpr], + **named_inputs: IntoExpr, +) -> list[PyExpr]: + """ + Parse multiple inputs into a list of expressions. + + Parameters + ---------- + *inputs + Inputs to be parsed as expressions, specified as positional arguments. + **named_inputs + Additional inputs to be parsed as expressions, specified as keyword arguments. + The expressions will be renamed to the keyword used. + + Returns + ------- + list of PyExpr + """ + exprs = _parse_positional_inputs(inputs) # type: ignore[arg-type] + if named_inputs: + named_exprs = _parse_named_inputs(named_inputs) + exprs.extend(named_exprs) + return exprs + + +def parse_into_list_of_expressions_require_selectors( + *inputs: IntoExpr | Iterable[IntoExpr], + **named_inputs: IntoExpr, +) -> list[PyExpr]: + """ + Parse multiple inputs (required to be valid selectors) into a list of expressions. + + Parameters + ---------- + *inputs + Inputs to be parsed as expressions, specified as positional arguments. + **named_inputs + Additional inputs to be parsed as expressions, specified as keyword arguments. + The expressions will be renamed to the keyword used. + + Returns + ------- + list of PyExpr + """ + exprs = _parse_positional_inputs( + inputs, # type: ignore[arg-type] + require_selectors=True, + ) + if named_inputs: + named_exprs = _parse_named_inputs(named_inputs) + exprs.extend(named_exprs) + return exprs + + +@overload +def parse_into_selector( + i: ColumnNameOrSelector, + *, + strict: bool = ..., + raise_if_not_selector: Literal[False] = False, +) -> pl.Selector: ... + + +@overload +def parse_into_selector( + i: ColumnNameOrSelector, + *, + strict: bool = ..., + raise_if_not_selector: Literal[True], +) -> pl.Selector | None: ... + + +def parse_into_selector( + i: ColumnNameOrSelector, + *, + strict: bool = True, + raise_if_not_selector: bool = True, +) -> pl.Selector | None: + if isinstance(i, str): + return pl.Selector._by_name( + names=[i], + strict=strict, + expand_patterns=True, + ) + elif isinstance(i, pl.Selector): + return i + elif isinstance(i, pl.Expr): + return i.meta.as_selector() + elif raise_if_not_selector: + msg = f"cannot turn {qualified_type_name(i)!r} into selector" + raise TypeError(msg) + return None + + +def parse_list_into_selector( + inputs: ColumnNameOrSelector | Collection[ColumnNameOrSelector], + *, + strict: bool = True, +) -> pl.Selector: + if isinstance(inputs, Collection) and not isinstance(inputs, str): + columns: list[str] = [i for i in inputs if isinstance(i, str)] + selector = pl.Selector._by_name( + names=columns, + strict=strict, + expand_patterns=True, + ) + if len(columns) == len(inputs): + return selector + + if len(columns) == 0: + import polars.selectors as cs + + selector = cs.empty() + + for i in inputs: + selector |= parse_into_selector(i, strict=strict) + return selector + else: + return parse_into_selector(inputs, strict=strict) + + +def _parse_positional_inputs( + inputs: tuple[IntoExpr, ...] | tuple[Iterable[IntoExpr]], + *, + require_selectors: bool = False, + structify: bool = False, +) -> list[PyExpr]: + inputs_iter = _parse_inputs_as_iterable(inputs) + return [ + parse_into_expression( + e, + structify=structify, + require_selector=require_selectors, + ) + for e in inputs_iter + ] + + +def _parse_inputs_as_iterable( + inputs: tuple[Any, ...] | tuple[Iterable[Any]], +) -> Iterable[Any]: + if not inputs: + return [] + + # Ensures that the outermost element cannot be a Dictionary (as an iterable) + if len(inputs) == 1 and isinstance(inputs[0], Mapping): + msg = ( + "Cannot pass a dictionary as a single positional argument.\n" + "If you merely want the *keys*, use:\n" + " • df.method(*your_dict.keys())\n" + "If you need the key value pairs, use one of:\n" + " • unpack as keywords: df.method(**your_dict)\n" + " • build expressions: df.method(expr.alias(k) for k, expr in your_dict.items())" + ) + raise TypeError(msg) + + # Treat elements of a single iterable as separate inputs + if len(inputs) == 1 and _is_iterable(inputs[0]): + return inputs[0] + + return inputs + + +def _is_iterable(input: Any) -> bool: + return isinstance(input, Iterable) and not isinstance( + input, (str, bytes, pl.Series) + ) + + +def _parse_named_inputs( + named_inputs: dict[str, IntoExpr], *, structify: bool = False +) -> Iterable[PyExpr]: + for name, input in named_inputs.items(): + yield parse_into_expression(input, structify=structify).alias(name) + + +def parse_predicates_constraints_into_expression( + *predicates: IntoExpr | Iterable[IntoExpr], + **constraints: Any, +) -> PyExpr: + """ + Parse predicates and constraints into a single expression. + + The result is an AND-reduction of all inputs. + + Parameters + ---------- + *predicates + Predicates to be parsed, specified as positional arguments. + **constraints + Constraints to be parsed, specified as keyword arguments. + These will be converted to predicates of the form "keyword equals input value". + + Returns + ------- + PyExpr + """ + all_predicates = _parse_positional_inputs(predicates) # type: ignore[arg-type] + + if constraints: + constraint_predicates = _parse_constraints(constraints) + all_predicates.extend(constraint_predicates) + + return _combine_predicates(all_predicates) + + +def _parse_constraints(constraints: dict[str, IntoExpr]) -> Iterable[PyExpr]: + for name, value in constraints.items(): + yield F.col(name).eq(value)._pyexpr + + +def _combine_predicates(predicates: list[PyExpr]) -> PyExpr: + if not predicates: + msg = "at least one predicate or constraint must be provided" + raise TypeError(msg) + + if len(predicates) == 1: + return predicates[0] + + return plr.all_horizontal(predicates) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/polars_version.py b/hug/lib/python3.12/site-packages/polars/_utils/polars_version.py new file mode 100644 index 0000000000000000000000000000000000000000..e1233564833132954316c29b1e6911a59744d006 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/polars_version.py @@ -0,0 +1,31 @@ +try: + import polars._plr as plr + + _POLARS_VERSION = plr.__version__ + _POLARS_BUILD_COMMIT = plr._BUILD_COMMIT +except ImportError: + # This is only useful for documentation + import warnings + + warnings.warn("Polars binary is missing!", stacklevel=2) + _POLARS_VERSION = "" + _POLARS_BUILD_COMMIT = "" + + +def get_polars_version() -> str: + """ + Return the version of the Python Polars package as a string. + + If the Polars binary is missing, returns an empty string. + """ + return _POLARS_VERSION + + +def get_polars_build_commit() -> str: + """ + Return the commit SHA that Polars was built from. + + This is only baked in by the release CI workflow; local and source builds + report ``. If the Polars binary is missing, returns an empty string. + """ + return _POLARS_BUILD_COMMIT diff --git a/hug/lib/python3.12/site-packages/polars/_utils/pycapsule.py b/hug/lib/python3.12/site-packages/polars/_utils/pycapsule.py new file mode 100644 index 0000000000000000000000000000000000000000..6dd06de934391e383a1f621bb718b76b2948ef3d --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/pycapsule.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import contextlib +from typing import TYPE_CHECKING, Any + +from polars._utils.construction.dataframe import dataframe_to_pydf +from polars._utils.wrap import wrap_df + +with contextlib.suppress(ImportError): + from polars._plr import PySeries + +if TYPE_CHECKING: + from polars import DataFrame + from polars._typing import SchemaDefinition, SchemaDict + + +def is_pycapsule(obj: Any) -> bool: + """Check if object looks like it supports the PyCapsule interface.""" + return any( + callable(getattr(obj, attr, None)) + for attr in ("__arrow_c_stream__", "__arrow_c_array__") + ) + + +def pycapsule_to_frame( + obj: Any, + *, + schema: SchemaDefinition | None = None, + schema_overrides: SchemaDict | None = None, + rechunk: bool = False, +) -> DataFrame: + """Convert PyCapsule object to DataFrame.""" + if hasattr(obj, "__arrow_c_array__"): + # This uses the fact that PySeries.from_arrow_c_array will create a + # struct-typed Series. Then we unpack that to a DataFrame. + # `struct_unnest` is a single Rust call that releases the GIL; going via + # `DataFrame.unnest` would run the whole lazy engine while holding it, + # which serializes construction across Python threads. + df = wrap_df(PySeries.from_arrow_c_array(obj).struct_unnest()) + + elif hasattr(obj, "__arrow_c_stream__"): + # This uses the fact that PySeries.from_arrow_c_stream will create a + # struct-typed Series. Then we unpack that to a DataFrame. + df = wrap_df(PySeries.from_arrow_c_stream(obj).struct_unnest()) + else: + msg = f"object does not support PyCapsule interface; found {obj!r} " + raise TypeError(msg) + + if rechunk: + df = df.rechunk() + if schema or schema_overrides: + df = wrap_df( + dataframe_to_pydf(df, schema=schema, schema_overrides=schema_overrides) + ) + return df diff --git a/hug/lib/python3.12/site-packages/polars/_utils/reduce_balanced.py b/hug/lib/python3.12/site-packages/polars/_utils/reduce_balanced.py new file mode 100644 index 0000000000000000000000000000000000000000..82f79d594b69e284adf2765e21fc6ed70aeffc3e --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/reduce_balanced.py @@ -0,0 +1,39 @@ +from collections.abc import Callable, Iterable +from typing import TypeVar + +T = TypeVar("T") + + +def reduce_balanced(function: Callable[[T, T], T], iterable: Iterable[T]) -> T: + """Applies a reduction in a balanced tree pattern.""" + values = list(iterable) + + if not values: + msg = "reduce_balanced() of empty iterable" + raise TypeError(msg) + + if len(values) == 1: + return values.pop() + + stack = [(0, len(values))] + + i = 0 + + while i < len(stack): + offset, length = stack[i] + half = -(length // -2) + + if length > 3: + stack.append((offset + half, length - half)) + + if length > 2: + stack.append((offset, half)) + + stack[i] = (offset, offset + half) + + i += 1 + + for idx_l, idx_r in reversed(stack): + values[idx_l] = function(values[idx_l], values[idx_r]) + + return values[0] diff --git a/hug/lib/python3.12/site-packages/polars/_utils/serde.py b/hug/lib/python3.12/site-packages/polars/_utils/serde.py new file mode 100644 index 0000000000000000000000000000000000000000..3e8fa84e1d378d696180ae67e41f4be0a3dc6f1e --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/serde.py @@ -0,0 +1,64 @@ +"""Utility for serializing Polars objects.""" + +from __future__ import annotations + +from io import BytesIO, StringIO +from pathlib import Path +from typing import TYPE_CHECKING, Literal, overload + +from polars._utils.various import normalize_filepath + +if TYPE_CHECKING: + from collections.abc import Callable + from io import IOBase + + from polars._typing import SerializationFormat + + +@overload +def serialize_polars_object( + serializer: Callable[[IOBase | str], None], file: None, format: Literal["binary"] +) -> bytes: ... +@overload +def serialize_polars_object( + serializer: Callable[[IOBase | str], None], file: None, format: Literal["json"] +) -> str: ... +@overload +def serialize_polars_object( + serializer: Callable[[IOBase | str], None], + file: IOBase | str | Path, + format: SerializationFormat, +) -> None: ... + + +def serialize_polars_object( + serializer: Callable[[IOBase | str], None], + file: IOBase | str | Path | None, + format: SerializationFormat, +) -> bytes | str | None: + """Serialize a Polars object (DataFrame/LazyFrame/Expr).""" + + def serialize_to_bytes() -> bytes: + with BytesIO() as buf: + serializer(buf) + serialized = buf.getvalue() + return serialized + + if file is None: + serialized = serialize_to_bytes() + return serialized.decode() if format == "json" else serialized + elif isinstance(file, StringIO): + serialized_str = serialize_to_bytes().decode() + file.write(serialized_str) + return None + elif isinstance(file, BytesIO): + serialized = serialize_to_bytes() + file.write(serialized) + return None + elif isinstance(file, (str, Path)): + file = normalize_filepath(file) + serializer(file) + return None + else: + serializer(file) + return None diff --git a/hug/lib/python3.12/site-packages/polars/_utils/slice.py b/hug/lib/python3.12/site-packages/polars/_utils/slice.py new file mode 100644 index 0000000000000000000000000000000000000000..a2e621307b8f0d607ae97c811fccbfb1d96cd789 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/slice.py @@ -0,0 +1,217 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import polars._reexport as pl + +if TYPE_CHECKING: + from typing import TypeAlias + + from polars import DataFrame, LazyFrame, Series + + FrameOrSeries: TypeAlias = DataFrame | Series + + +class PolarsSlice: + """ + Apply Python slice object to Polars DataFrame or Series. + + Has full support for negative indexing and/or stride. + """ + + stop: int + start: int + stride: int + slice_length: int + is_unbounded: bool + obj: FrameOrSeries + + def __init__(self, obj: FrameOrSeries) -> None: + self.obj = obj + + @staticmethod + def _as_original(lazy: LazyFrame, original: FrameOrSeries) -> FrameOrSeries: + """Return lazy variant back to its original type.""" + frame = lazy._collect_eager() + return frame if isinstance(original, pl.DataFrame) else frame.to_series() + + @staticmethod + def _lazify(obj: FrameOrSeries) -> LazyFrame: + """Make lazy to ensure efficient/consistent handling.""" + return obj.to_frame().lazy() if isinstance(obj, pl.Series) else obj.lazy() + + def _slice_positive(self, obj: LazyFrame) -> LazyFrame: + """Logic for slices with positive stride.""" + # note: at this point stride is guaranteed to be > 1 + return obj.slice(self.start, self.slice_length).gather_every(self.stride) + + def _slice_negative(self, obj: LazyFrame) -> LazyFrame: + """Logic for slices with negative stride.""" + stride = abs(self.stride) + lazyslice = obj.slice(self.stop + 1, self.slice_length).reverse() + return lazyslice.gather_every(stride) if (stride > 1) else lazyslice + + def _slice_setup(self, s: slice) -> None: + """Normalise slice bounds, identify unbounded and/or zero-length slices.""" + # can normalise slice indices as we know object size + obj_len = len(self.obj) + start, stop, stride = slice(s.start, s.stop, s.step).indices(obj_len) + + # check if slice is actually unbounded + if stride >= 1: + self.is_unbounded = (start <= 0) and (stop >= obj_len) + else: + self.is_unbounded = (stop == -1) and (start >= obj_len - 1) + + # determine slice length + if self.obj.is_empty(): + self.slice_length = 0 + elif self.is_unbounded: + self.slice_length = obj_len + else: + self.slice_length = ( + 0 + if ( + (start == stop) + or (stride > 0 and start > stop) + or (stride < 0 and start < stop) + ) + else abs(stop - start) + ) + self.start, self.stop, self.stride = start, stop, stride + + def apply(self, s: slice) -> FrameOrSeries: + """Apply a slice operation, taking advantage of any potential fast paths.""" + # normalise slice + self._slice_setup(s) + + # check for fast-paths / single-operation calls + if self.slice_length == 0: + return self.obj.clear() + + elif self.is_unbounded and self.stride in (-1, 1): + return self.obj.reverse() if (self.stride < 0) else self.obj.clone() + + elif self.start >= 0 and self.stop >= 0 and self.stride == 1: + return self.obj.slice(self.start, self.slice_length) + + elif self.stride < 0 and self.slice_length == 1: + return self.obj.slice(self.stop + 1, 1) + else: + # multi-operation calls; make lazy + lazyobj = self._lazify(self.obj) + sliced = ( + self._slice_positive(lazyobj) + if self.stride > 0 + else self._slice_negative(lazyobj) + ) + return self._as_original(sliced, self.obj) + + +class LazyPolarsSlice: + """ + Apply python slice object to Polars LazyFrame. + + Only slices with efficient computation paths that map directly + to existing lazy methods are supported. + """ + + obj: LazyFrame + + def __init__(self, obj: LazyFrame) -> None: + self.obj = obj + + def apply(self, s: slice) -> LazyFrame: + """ + Apply a slice operation. + + Note that LazyFrame is designed primarily for efficient computation and does not + know its own length so, unlike DataFrame, certain slice patterns (such as those + requiring negative stop/step) may not be supported. + """ + start = s.start or 0 + step = s.step or 1 + + # fail on operations that require length to do efficiently + if s.stop and s.stop < 0: + msg = "negative stop is not supported for lazy slices" + raise ValueError(msg) + if step < 0 and (start > 0 or s.stop is not None) and (start != s.stop): + if not (start > 0 > step and s.stop is None): + msg = "negative stride is not supported in conjunction with start+stop" + raise ValueError(msg) + + # --------------------------------------- + # empty slice patterns + # --------------------------------------- + # [:0] + # [i:<=i] + # [i:>=i:-k] + if (step > 0 and (s.stop is not None and start >= s.stop)) or ( + step < 0 + and (s.start is not None and s.stop is not None and s.stop >= s.start >= 0) + ): + return self.obj.clear() + + # --------------------------------------- + # straight-through mappings for "reverse" + # and/or "gather_every" + # --------------------------------------- + # [:] => clone() + # [::k] => gather_every(k), + # [::-1] => reverse(), + # [::-k] => reverse().gather_every(abs(k)) + elif s.start is None and s.stop is None: + if step == 1: + return self.obj.clone() + elif step > 1: + return self.obj.gather_every(step) + elif step == -1: + return self.obj.reverse() + elif step < -1: + return self.obj.reverse().gather_every(abs(step)) + + # --------------------------------------- + # straight-through mappings for "head", + # "reverse" and "gather_every" + # --------------------------------------- + # [i::-1] => head(i+1).reverse() + # [i::k], k<-1 => head(i+1).reverse().gather_every(abs(k)) + elif start >= 0 > step and s.stop is None: + obj = self.obj.head(s.start + 1).reverse() + return obj if (abs(step) == 1) else obj.gather_every(abs(step)) + + # --------------------------------------- + # straight-through mappings for "head" + # --------------------------------------- + # [:j] => head(j) + # [:j:k] => head(j).gather_every(k) + elif start == 0 and (s.stop or 0) >= 1: + obj = self.obj.head(s.stop) + return obj if (step == 1) else obj.gather_every(step) + + # --------------------------------------- + # straight-through mappings for "tail" + # --------------------------------------- + # [-i:] => tail(abs(i)) + # [-i::k] => tail(abs(i)).gather_every(k) + elif start < 0 and s.stop is None and step > 0: + obj = self.obj.tail(abs(start)) + return obj if (step == 1) else obj.gather_every(step) + + # --------------------------------------- + # straight-through mappings for "slice" + # --------------------------------------- + # [i:] => slice(i) + # [i:j] => slice(i,j-i) + # [i:j:k] => slice(i,j-i).gather_every(k) + elif start > 0 and (s.stop is None or s.stop >= 0): + slice_length = None if (s.stop is None) else (s.stop - start) + obj = self.obj.slice(start, slice_length) + return obj if (step == 1) else obj.gather_every(step) + + msg = ( + f"the given slice {s!r} is not supported by lazy computation" + "\n\nConsider a more efficient approach, or construct explicitly with other methods." + ) + raise ValueError(msg) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/threading.py b/hug/lib/python3.12/site-packages/polars/_utils/threading.py new file mode 100644 index 0000000000000000000000000000000000000000..fd2cf06c993138e95c0bf9d67d02937b31d0e293 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/threading.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from concurrent.futures import ThreadPoolExecutor +from typing import TYPE_CHECKING, Generic, ParamSpec, TypeVar + +if TYPE_CHECKING: + from collections.abc import Callable + +P = ParamSpec("P") +T = TypeVar("T") + + +# Binds a function with a thread pool. +# Used from Rust, to allow the following: +# (py, func, args, kwargs, pool) +# -> FnPoolWrap.call0(py, func, pool).call(py, args, kwargs) +class FnPoolWrap(Generic[P, T]): + def __init__(self, f: Callable[P, T], pool_wrap: PyThreadPool) -> None: + self.f = f + self.pool_wrap = pool_wrap + + def __call__(self, *a: P.args, **kw: P.kwargs) -> T: + try: + return self.pool_wrap.pool.submit(self.f, *a, **kw).result() + except BaseException as e: + if self.pool_wrap.last_exception is None: + self.pool_wrap.last_exception = e + + # Shutdown, otherwise exception doesn't get raised until all tasks + # finish. + self.pool_wrap.pool.shutdown(wait=False, cancel_futures=True) + + raise self.pool_wrap.last_exception from e + + +class PyThreadPool: + def __init__(self, num_threads: int) -> None: + self.pool = ThreadPoolExecutor(num_threads) + self.last_exception: BaseException | None = None diff --git a/hug/lib/python3.12/site-packages/polars/_utils/udfs.py b/hug/lib/python3.12/site-packages/polars/_utils/udfs.py new file mode 100644 index 0000000000000000000000000000000000000000..1e8f1012bc970de283e916428bf5b98266c006f6 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/udfs.py @@ -0,0 +1,1348 @@ +"""Utilities related to user defined functions (such as those passed to `apply`).""" + +from __future__ import annotations + +import datetime +import dis +import inspect +import re +import sys +import types +import warnings +from bisect import bisect_left +from collections import defaultdict +from collections.abc import Collection +from dis import get_instructions +from enum import Enum, auto +from inspect import signature +from itertools import count, zip_longest +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Final, + Literal, + NamedTuple, + TypedDict, +) + +from polars._dependencies import _check_for_pandas +from polars._utils.cache import LRUCache +from polars._utils.various import NO_DEFAULT, re_escape +from polars.dataframe.frame import DataFrame +from polars.datatypes import List, String + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable, Iterator, MutableMapping + from collections.abc import Set as AbstractSet + from dis import Instruction + from typing import TypeAlias + + from polars._typing import PolarsDataType + from polars._utils.various import NoDefault + + +class StackValue(NamedTuple): + operator: str + operator_arity: int + left_operand: str + right_operand: str + from_module: str | None = None + + +class ContainmentKind(Enum): + """Type of operand for `in` and `not in` operators.""" + + COLLECTION = auto() + STRING = auto() + COLUMN = auto() + + +MapTarget: TypeAlias = Literal["expr", "frame", "series"] +StackEntry: TypeAlias = str | StackValue + +_MIN_PY312: Final = sys.version_info >= (3, 12) +_MIN_PY314: Final = _MIN_PY312 and sys.version_info >= (3, 14) + +_BYTECODE_PARSER_CACHE_: MutableMapping[ + tuple[Callable[[Any], Any], str], + BytecodeParser, +] = LRUCache(64) +_INSTRUCTIONS_CACHE_: MutableMapping[ + types.CodeType, + list[Instruction], +] = LRUCache(64) + + +class OpNames: + CALL = frozenset({"CALL"}) + CONTROL_FLOW: ClassVar[dict[str, str]] = ( + { + "POP_JUMP_FORWARD_IF_FALSE": "&", + "POP_JUMP_FORWARD_IF_TRUE": "|", + "JUMP_IF_FALSE_OR_POP": "&", + "JUMP_IF_TRUE_OR_POP": "|", + } + # note: 3.12 dropped POP_JUMP_FORWARD_IF_* opcodes + if not _MIN_PY312 + else { + "POP_JUMP_IF_FALSE": "&", + "POP_JUMP_IF_TRUE": "|", + "JUMP_IF_FALSE_OR_POP": "&", + "JUMP_IF_TRUE_OR_POP": "|", + } + ) + LOAD_VALUES = frozenset(("LOAD_CONST", "LOAD_DEREF", "LOAD_FAST", "LOAD_GLOBAL")) + LOAD_METHOD = frozenset({"LOAD_METHOD"}) + LOAD_ATTR = frozenset({"LOAD_ATTR"}) | LOAD_METHOD + LOAD = LOAD_VALUES | LOAD_ATTR + SIMPLIFY_SPECIALIZED: ClassVar[dict[str, str]] = { + "LOAD_COMMON_CONSTANT": "LOAD_CONST", + "LOAD_FAST_BORROW": "LOAD_FAST", + "LOAD_SMALL_INT": "LOAD_CONST", + } + SYNTHETIC: ClassVar[dict[str, int]] = { + "POLARS_EXPRESSION": 1, + } + UNARY: ClassVar[dict[str, str]] = { + "UNARY_NEGATIVE": "-", + "UNARY_POSITIVE": "+", + "UNARY_NOT": "~", + } + PARSEABLE_OPS = frozenset( + {"BINARY_OP", "BINARY_SUBSCR", "COMPARE_OP", "CONTAINS_OP", "IS_OP"} + | set(UNARY) + | set(CONTROL_FLOW) + | set(SYNTHETIC) + | LOAD_VALUES + ) + MATCHABLE_OPS = set(SIMPLIFY_SPECIALIZED) | PARSEABLE_OPS | LOAD_ATTR | CALL + UNARY_VALUES = frozenset(UNARY.values()) + + +# math module funcs that we can map to native expressions +_MATH_FUNCTIONS: Final[frozenset[str]] = frozenset( + ( + "acos", + "acosh", + "asin", + "asinh", + "atan", + "atanh", + "cbrt", + "ceil", + "cos", + "cosh", + "degrees", + "erf", + "erfc", + "exp", + "floor", + "log", + "log10", + "log1p", + "pow", + "radians", + "sin", + "sinh", + "sqrt", + "tan", + "tanh", + ) +) + +# numpy functions that we can map to native expressions +_NUMPY_MODULE_ALIASES: Final[frozenset[str]] = frozenset(("np", "numpy")) +_NUMPY_FUNCTIONS: Final[frozenset[str]] = frozenset( + ( + # "abs", # TODO: this one clashes with Python builtin abs + "arccos", + "arccosh", + "arcsin", + "arcsinh", + "arctan", + "arctanh", + "cbrt", + "ceil", + "cos", + "cosh", + "degrees", + "exp", + "floor", + "log", + "log10", + "log1p", + "radians", + "sign", + "sin", + "sinh", + "sqrt", + "tan", + "tanh", + ) +) + +# python attrs/funcs that map to native expressions +_PYTHON_ATTRS_MAP: Final[dict[str, str]] = { + "date": "dt.date()", + "day": "dt.day()", + "hour": "dt.hour()", + "microsecond": "dt.microsecond()", + "minute": "dt.minute()", + "month": "dt.month()", + "second": "dt.second()", + "year": "dt.year()", +} +_PYTHON_CASTS_MAP: Final[dict[str, str]] = { + "float": "Float64", + "int": "Int64", + "str": "String", +} +_PYTHON_BUILTINS: Final[frozenset[str]] = frozenset(_PYTHON_CASTS_MAP) | {"abs"} +_PYTHON_METHODS_MAP: Final[dict[str, str]] = { + # string + "endswith": "str.ends_with", + "lower": "str.to_lowercase", + "lstrip": "str.strip_chars_start", + "removeprefix": "str.strip_prefix", + "removesuffix": "str.strip_suffix", + "replace": "str.replace", + "rstrip": "str.strip_chars_end", + "startswith": "str.starts_with", + "strip": "str.strip_chars", + "title": "str.to_titlecase", + "upper": "str.to_uppercase", + "zfill": "str.zfill", + # temporal + "date": "dt.date", + "day": "dt.day", + "hour": "dt.hour", + "isoweekday": "dt.weekday", + "microsecond": "dt.microsecond", + "month": "dt.month", + "second": "dt.second", + "strftime": "dt.strftime", + "time": "dt.time", + "year": "dt.year", +} + + +class ModuleFunction(TypedDict, total=False): + argument_1_opname: list[AbstractSet[str]] + argument_2_opname: list[AbstractSet[str]] + argument_1_unary_opname: list[AbstractSet[str]] + argument_2_unary_opname: list[AbstractSet[str]] + module_opname: list[AbstractSet[str]] + attribute_opname: list[AbstractSet[str]] + module_name: list[AbstractSet[str]] + attribute_name: list[AbstractSet[str]] + function_name: list[AbstractSet[str]] + check_load_global: bool + + +_MODULE_FUNCTIONS: list[ModuleFunction] = [ + # lambda x: numpy.func(x) + # lambda x: numpy.func(CONSTANT) + { + "argument_1_opname": [{"LOAD_FAST", "LOAD_CONST"}], + "argument_2_opname": [], + "module_opname": [OpNames.LOAD_ATTR], + "attribute_opname": [], + "module_name": [_NUMPY_MODULE_ALIASES], + "attribute_name": [], + "function_name": [_NUMPY_FUNCTIONS], + }, + # lambda x: math.func(x) + # lambda x: math.func(CONSTANT) + { + "argument_1_opname": [{"LOAD_FAST", "LOAD_CONST"}], + "argument_2_opname": [], + "module_opname": [OpNames.LOAD_ATTR], + "attribute_opname": [], + "module_name": [{"math"}], + "attribute_name": [], + "function_name": [_MATH_FUNCTIONS], + }, + # lambda x: json.loads(x) + { + "argument_1_opname": [{"LOAD_FAST"}], + "argument_2_opname": [], + "module_opname": [OpNames.LOAD_ATTR], + "attribute_opname": [], + "module_name": [{"json"}], + "attribute_name": [], + "function_name": [{"loads"}], + }, + # lambda x: datetime.strptime(x, CONSTANT) + { + "argument_1_opname": [{"LOAD_FAST"}], + "argument_2_opname": [{"LOAD_CONST"}], + "module_opname": [OpNames.LOAD_ATTR], + "attribute_opname": [], + "module_name": [{"datetime"}], + "attribute_name": [], + "function_name": [{"strptime"}], + "check_load_global": False, + }, + # lambda x: module.attribute.func(x, CONSTANT) + { + "argument_1_opname": [{"LOAD_FAST"}], + "argument_2_opname": [{"LOAD_CONST"}], + "module_opname": [{"LOAD_ATTR"}], + "attribute_opname": [OpNames.LOAD_ATTR], + "module_name": [{"datetime", "dt"}], + "attribute_name": [{"datetime"}], + "function_name": [{"strptime"}], + "check_load_global": False, + }, +] +# In addition to `lambda x: func(x)`, also support cases when a unary operation +# has been applied to `x`, like `lambda x: func(-x)` or `lambda x: func(~x)`. +_UNARIES: list[list[AbstractSet[str]]] = [[set(OpNames.UNARY)], []] +_MODULE_FUNCTIONS = [ + {**kind, "argument_1_unary_opname": unary} + for kind in _MODULE_FUNCTIONS + for unary in _UNARIES +] +# Lookup for module functions that have different names as polars expressions +_MODULE_FUNC_TO_EXPR_NAME: Final[dict[str, str]] = { + "math.acos": "arccos", + "math.acosh": "arccosh", + "math.asin": "arcsin", + "math.asinh": "arcsinh", + "math.atan": "arctan", + "math.atanh": "arctanh", + "json.loads": "str.json_decode", +} +_RE_IMPLICIT_BOOL: Final = re.compile(r'pl\.col\("([^"]*)"\) & pl\.col\("\1"\)\.(.+)') +_RE_SERIES_NAMES: Final = re.compile(r"^(s|srs\d?|series)\.") +_RE_STRIP_BOOL: Final = re.compile(r"^bool\((.+)\)$") + + +def _get_function_variable(function: Callable[[Any], Any], name: str) -> Any: + """Resolve a name from the function's lexical scope (closure, then globals).""" + code = getattr(function, "__code__", None) + closure = getattr(function, "__closure__", None) + if code is not None and closure is not None and name in code.co_freevars: + try: + return closure[code.co_freevars.index(name)].cell_contents + except ValueError: # empty cell + return NO_DEFAULT + return getattr(function, "__globals__", {}).get(name, NO_DEFAULT) + + +def _get_target_name(col: str, expression: str, map_target: str) -> str: + """The name of the object against which the 'map' is being invoked.""" + col_expr = f'pl.col("{col}")' + if map_target == "expr": + return col_expr + elif map_target == "series": + if _RE_SERIES_NAMES.match(expression): + return expression.split(".", 1)[0] + + # note: handle overlapping name from global variables; fallback + # through "s", "srs", "series" and (finally) srs0 -> srsN... + search_expr = expression.replace(col_expr, "") + for name in ("s", "srs", "series"): + if not re.search(rf"\b{name}\b", search_expr): + return name + n = count() + while True: + name = f"srs{next(n)}" + if not re.search(rf"\b{name}\b", search_expr): + return name + + msg = f"TODO: map_target = {map_target!r}" + raise NotImplementedError(msg) + + +class BytecodeParser: + """Introspect UDF bytecode and determine if we can rewrite as native expression.""" + + _map_target_name: str | None = None + _can_attempt_rewrite: bool | None = None + _skipped_disassembly: bool = False + _col_expression: tuple[str, str] | NoDefault | None = NO_DEFAULT + + def __init__(self, function: Callable[[Any], Any], map_target: MapTarget) -> None: + """ + Initialize BytecodeParser instance and prepare to introspect UDFs. + + Parameters + ---------- + function : callable + The function/lambda to disassemble and introspect. + map_target : {'expr','series','frame'} + The underlying target object type of the map operation. + """ + original_instructions: Iterable[Instruction] + try: + if type(function) is types.FunctionType: + code = function.__code__ + if code.co_code[::2].translate(None, _REWRITABLE_OPCODES): + # bytecode contains ops we cannot rewrite, so skip disassembly + self._skipped_disassembly = True + original_instructions = [] + elif (cached := _INSTRUCTIONS_CACHE_.get(code)) is not None: + original_instructions = cached + else: + original_instructions = list(get_instructions(code)) + _INSTRUCTIONS_CACHE_[code] = original_instructions + else: + original_instructions = get_instructions(function) + except TypeError: + # in case we hit something that can't be disassembled (eg: code object + # unavailable, like a bare numpy ufunc that isn't in a lambda/function) + original_instructions = iter([]) + + self._function = function + self._map_target = map_target + self._param_name = self._get_param_name(function) + self._rewritten_instructions = RewrittenInstructions( + instructions=original_instructions, + function=function, + ) + + def _omit_implicit_bool(self, expr: str) -> str: + """Drop extraneous/implied bool (eg: `pl.col("d") & pl.col("d").dt.date()`).""" + while _RE_IMPLICIT_BOOL.search(expr): + expr = _RE_IMPLICIT_BOOL.sub(repl=r'pl.col("\1").\2', string=expr) + return expr + + @staticmethod + def _get_param_name(function: Callable[[Any], Any]) -> str | None: + """Return single function parameter name.""" + if type(function) is types.FunctionType: + if ( + not hasattr(function, "__signature__") + and not hasattr(function, "__wrapped__") + and not hasattr(function, "__text_signature__") + ): + code = function.__code__ + n_params = ( + code.co_argcount + + code.co_kwonlyargcount + + bool(code.co_flags & inspect.CO_VARARGS) # +1 for *args + + bool(code.co_flags & inspect.CO_VARKEYWORDS) # +1 for **kwargs + ) + return code.co_varnames[0] if n_params == 1 else None + + try: + sig = signature(function) + except ValueError: + return None + return ( + next(iter(parameters.keys())) + if len(parameters := sig.parameters) == 1 + else None + ) + + def _inject_nesting( + self, + expression_blocks: dict[int, str], + logical_instructions: list[Instruction], + ) -> list[tuple[int, str]]: + """Inject nesting boundaries into expression blocks (as parentheses).""" + if logical_instructions: + # reconstruct nesting for mixed 'and'/'or' ops by associating control flow + # jump offsets with their target expression blocks and applying parens + if len({inst.opname for inst in logical_instructions}) > 1: + block_offsets: list[int] = list(expression_blocks.keys()) + prev_end = -1 + for inst in logical_instructions: + start = block_offsets[bisect_left(block_offsets, inst.offset) - 1] + end = block_offsets[bisect_left(block_offsets, inst.argval) - 1] + if not (start == 0 and end == block_offsets[-1]): + if prev_end not in (start, end): + expression_blocks[start] = "(" + expression_blocks[start] + expression_blocks[end] += ")" + prev_end = end + + for inst in logical_instructions: # inject connecting "&" and "|" ops + expression_blocks[inst.offset] = OpNames.CONTROL_FLOW[inst.opname] + + return sorted(expression_blocks.items()) + + @property + def map_target(self) -> MapTarget: + """The map target, eg: one of 'expr', 'frame', or 'series'.""" + return self._map_target + + def can_attempt_rewrite(self) -> bool: + """ + Determine if we may be able to offer a native polars expression instead. + + Note that `lambda x: x` is inefficient, but we ignore it because it is not + guaranteed that using the equivalent bare constant value will return the + same output. (Hopefully nobody is writing lambdas like that anyway...) + """ + if self._can_attempt_rewrite is None: + self._can_attempt_rewrite = ( + self._param_name is not None + # check minimum number of ops, ensuring all are parseable + and len(self._rewritten_instructions) >= 2 + and all( + inst.opname in OpNames.PARSEABLE_OPS + for inst in self._rewritten_instructions + ) + # exclude constructs/functions with multiple RETURN_VALUE ops + and sum( + 1 + for inst in self.original_instructions + if inst.opname == "RETURN_VALUE" + ) + == 1 + ) + return self._can_attempt_rewrite + + def dis(self) -> None: + """Print disassembled function bytecode.""" + dis.dis(self._function) + + @property + def function(self) -> Callable[[Any], Any]: + """The function being parsed.""" + return self._function + + @property + def original_instructions(self) -> list[Instruction]: + """The original bytecode instructions from the function we are parsing.""" + if self._skipped_disassembly: + return list(get_instructions(self._function)) + return list(self._rewritten_instructions._original_instructions) + + @property + def param_name(self) -> str | None: + """The parameter name of the function being parsed.""" + return self._param_name + + @property + def rewritten_instructions(self) -> list[Instruction]: + """The rewritten bytecode instructions from the function we are parsing.""" + return list(self._rewritten_instructions) + + def to_expression( + self, col: str, dtype: PolarsDataType | None = None + ) -> str | None: + """Translate postfix bytecode instructions to polars expression/string.""" + if self._col_expression is not NO_DEFAULT and self._col_expression is not None: + col_name, expr = self._col_expression + if col != col_name: + expr = re.sub( + rf'pl\.col\("{re_escape(col_name)}"\)', + f'pl.col("{re_escape(col)}")', + expr, + ) + self._col_expression = (col, expr) + return expr + + self._map_target_name = None + if self._param_name is None: + self._col_expression = None + return None + + # decompose bytecode into logical 'and'/'or' expression blocks (if present) + control_flow_blocks = defaultdict(list) + logical_instructions = [] + jump_offset = 0 + for idx, inst in enumerate(self._rewritten_instructions): + if inst.opname in OpNames.CONTROL_FLOW: + jump_offset = self._rewritten_instructions[idx + 1].offset + logical_instructions.append(inst) + else: + control_flow_blocks[jump_offset].append(inst) + + # convert each block to a polars expression string + try: + expression_strings = self._inject_nesting( + { + offset: InstructionTranslator( + instructions=ops, + map_target=self._map_target, + function=self._function, + dtype=dtype, + ).to_expression( + col=col, + param_name=self._param_name, + depth=int(bool(logical_instructions)), + ) + for offset, ops in control_flow_blocks.items() + }, + logical_instructions, + ) + except NotImplementedError: + self._col_expression = None + return None + + polars_expr = " ".join(expr for _offset, expr in expression_strings) + + # note: if no 'pl.col' in the expression, it likely represents a compound + # constant value (e.g. `lambda x: CONST + 123`), so we don't want to warn + if "pl.col(" not in polars_expr: + self._col_expression = None + return None + else: + polars_expr = self._omit_implicit_bool(polars_expr) + if self._map_target == "series": + if (target_name := self._map_target_name) is None: + target_name = _get_target_name(col, polars_expr, self._map_target) + self._map_target_name = target_name + polars_expr = polars_expr.replace(f'pl.col("{col}")', target_name) + + # ('in' translation depends on the operand values/dtype at call time) + if all(i.opname != "CONTAINS_OP" for i in self._rewritten_instructions): + self._col_expression = (col, polars_expr) + return polars_expr + + def warn( + self, + col: str, + *, + dtype: PolarsDataType | None = None, + suggestion_override: str | None = None, + udf_override: str | None = None, + ) -> None: + """Generate warning that suggests an equivalent native polars expression.""" + # import these here so UDFs can be imported without polars installed. + from polars._utils.various import ( + in_terminal_that_supports_colour, + ) + from polars._warnings import find_stacklevel + from polars.exceptions import PolarsInefficientMapWarning + + suggested_expression = suggestion_override or self.to_expression(col, dtype) + + if suggested_expression is not None: + if (target_name := self._map_target_name) is None: + target_name = _get_target_name( + col, suggested_expression, self._map_target + ) + func_name = udf_override or self._function.__name__ or "..." + if func_name == "": + func_name = f"lambda {self._param_name}: ..." + + if 'pl.col("")' in suggested_expression: + addendum = 'Note: in list.eval context, pl.col("") should be written as pl.element()' + elif ( + f'pl.col("{col}").str.contains(' in suggested_expression + and "literal=True" in suggested_expression + ): + addendum = f'Note: if "{col}" is a List column, use .list.contains instead of .str.contains' + else: + addendum = "" + + apitype, clsname = ( + ("expressions", "Expr") + if self._map_target == "expr" + else ("series", "Series") + ) + before, after = ( + ( + f" \033[31m- {target_name}.map_elements({func_name})\033[0m\n", + f" \033[32m+ {suggested_expression}\033[0m\n{addendum}", + ) + if in_terminal_that_supports_colour() + else ( + f" - {target_name}.map_elements({func_name})\n", + f" + {suggested_expression}\n{addendum}", + ) + ) + warnings.warn( + f"\n{clsname}.map_elements is significantly slower than the native {apitype} API.\n" + "Only use if you absolutely CANNOT implement your logic otherwise.\n" + "Replace this expression...\n" + f"{before}" + "with this one instead:\n" + f"{after}", + PolarsInefficientMapWarning, + stacklevel=find_stacklevel(), + ) + + +class InstructionTranslator: + """Translates Instruction bytecode to a polars expression string.""" + + def __init__( + self, + instructions: list[Instruction], + function: Callable[[Any], Any], + map_target: MapTarget, + dtype: PolarsDataType | None = None, + ) -> None: + self._constants: dict[str, Any] = {} + self._stack = self._to_intermediate_stack(instructions, map_target) + self._function = function + self._map_target = map_target + self._dtype = dtype + + def _containment_kind( + self, operand: StackEntry, param_name: str + ) -> ContainmentKind | None: + """Classify an `in` operand without evaluating user code.""" + if isinstance(operand, StackValue): + left = self._containment_kind(operand.left_operand, param_name) + right = ( + self._containment_kind(operand.right_operand, param_name) + if operand.operator_arity == 2 + else None + ) + if ContainmentKind.COLUMN in (left, right): + return ContainmentKind.COLUMN + return left if operand.operator == "+" and left is right else None + + if operand == param_name: + return ContainmentKind.COLUMN + if operand in self._constants: + value = self._constants[operand] + elif operand.isidentifier(): + value = _get_function_variable(self._function, operand) + else: + return None + + if isinstance(value, str): + return ContainmentKind.STRING + if isinstance(value, Collection) and not isinstance(value, (bytes, bytearray)): + # (pandas and frame membership tests labels, not values) + if getattr(value, "ndim", 1) == 1 and not ( + _check_for_pandas(value) or isinstance(value, DataFrame) + ): + return ContainmentKind.COLLECTION + return None + + def to_expression(self, col: str, param_name: str, depth: int) -> str: + """Convert intermediate stack to polars expression string.""" + return self._expr(self._stack, col, param_name, depth) + + @staticmethod + def op(inst: Instruction) -> str: + """Convert bytecode instruction to suitable intermediate op string.""" + if (opname := inst.opname) in OpNames.CONTROL_FLOW: + return OpNames.CONTROL_FLOW[opname] + elif inst.argrepr: + return inst.argrepr + elif opname == "IS_OP": + return "is not" if inst.argval else "is" + elif opname == "CONTAINS_OP": + return "not in" if inst.argval else "in" + elif opname in OpNames.UNARY: + return OpNames.UNARY[opname] + elif opname == "BINARY_SUBSCR": + return "replace_strict" + else: + msg = ( + f"unexpected or unrecognised op name ({opname})\n\n" + "Please report a bug to https://github.com/pola-rs/polars/issues " + "with the content of function you were passing to the `map` " + f"expression and the following instruction object:\n{inst!r}" + ) + raise AssertionError(msg) + + def _expr(self, value: StackEntry, col: str, param_name: str, depth: int) -> str: + """Take stack entry value and convert to polars expression string.""" + if isinstance(value, StackValue): + op = _RE_STRIP_BOOL.sub(r"\1", value.operator) + e1 = self._expr(value.left_operand, col, param_name, depth + 1) + if value.operator_arity == 1: + if op not in OpNames.UNARY_VALUES: + if e1.startswith("pl.col("): + call = "" if op.endswith(")") else "()" + return f"{e1}.{op}{call}" + if e1[0] in OpNames.UNARY_VALUES and e1[1:].startswith("pl.col("): + call = "" if op.endswith(")") else "()" + return f"({e1}).{op}{call}" + + # support use of consts as numpy/builtin params, eg: + # "np.sin(3) + np.cos(x)", or "len('const_string') + len(x)" + if ( + value.from_module in _NUMPY_MODULE_ALIASES + and op in _NUMPY_FUNCTIONS + ): + pfx = "np." + elif ( + value.from_module == "math" + and _MODULE_FUNC_TO_EXPR_NAME.get(f"math.{op}", op) + in _MATH_FUNCTIONS + ): + pfx = "math." + else: + pfx = "" + return f"{pfx}{op}({e1})" + return f"{op}{e1}" + else: + e2 = self._expr(value.right_operand, col, param_name, depth + 1) + if op in ("is", "is not") and value.left_operand == "None": + not_ = "" if op == "is" else "not_" + return f"{e1}.is_{not_}null()" + elif op in ("in", "not in"): + not_ = "" if op == "in" else "~" + kind = self._containment_kind(value.right_operand, param_name) + if kind is ContainmentKind.COLLECTION: + return ( + f"{not_}({e1}.is_in({e2}))" + if " " in e1 + else f"{not_}{e1}.is_in({e2})" + ) + needle = self._containment_kind(value.left_operand, param_name) + if kind is None or needle in (None, ContainmentKind.COLLECTION): + msg = "cannot determine operand types for 'in'" + raise NotImplementedError(msg) + + suffix = "" + if kind is ContainmentKind.COLUMN: + if ( + self._dtype is not None + and value.right_operand == param_name + ): + if self._dtype == List: + return f"{not_}{e2}.list.contains({e1})" + if self._dtype != String: + msg = ( + f"'in' is not a substring search for {self._dtype}" + ) + raise NotImplementedError(msg) + + haystack = e2 + elif self._map_target == "series": + haystack = f'pl.Series(pl.col("{col}").name, [{e2}])' + else: + haystack, suffix = f"pl.lit({e2})", f'.alias("{col}")' + return f"{not_}{haystack}.str.contains({e1}, literal=True){suffix}" + elif op == "replace_strict": + if not isinstance(_get_function_variable(self._function, e1), dict): + msg = "require dict mapping" + raise NotImplementedError(msg) + return f"{e2}.{op}({e1})" + elif op == "<<": + # 2**e2 may be float if e2 was -ve, but if e1 << e2 was valid then + # e2 must have been +ve. therefore 2**e2 can be safely cast to + # i64, which may be necessary if chaining ops that assume i64. + return f"({e1} * 2**{e2}).cast(pl.Int64)" + elif op == ">>": + # (motivation for the cast is same as the '<<' case above) + return f"({e1} / 2**{e2}).cast(pl.Int64)" + else: + expr = f"{e1} {op} {e2}" + return f"({expr})" if depth else expr + + elif value == param_name: + return f'pl.col("{col}")' + + return value + + def _to_intermediate_stack( + self, instructions: list[Instruction], map_target: MapTarget + ) -> StackEntry: + """Take postfix bytecode and convert to an intermediate natural-order stack.""" + if map_target in ("expr", "series"): + stack: list[StackEntry] = [] + for inst in instructions: + if inst.opname == "LOAD_CONST": + self._constants[inst.argrepr] = inst.argval + stack.append( + inst.argrepr + if inst.opname in OpNames.LOAD + else ( + StackValue( + operator=self.op(inst), + operator_arity=1, + left_operand=stack.pop(), # type: ignore[arg-type] + right_operand=None, # type: ignore[arg-type] + from_module=getattr(inst, "_from_module", None), + ) + if ( + inst.opname in OpNames.UNARY + or OpNames.SYNTHETIC.get(inst.opname) == 1 + ) + else StackValue( + operator=self.op(inst), + operator_arity=2, + left_operand=stack.pop(-2), # type: ignore[arg-type] + right_operand=stack.pop(-1), # type: ignore[arg-type] + from_module=getattr(inst, "_from_module", None), + ) + ) + ) + return stack[0] + + # TODO: dataframe.map... ? + msg = f"TODO: {map_target!r} map target not yet supported." + raise NotImplementedError(msg) + + +# The first opcodes of the patterns matched by each rewrite rule... +_START_METHOD_REWRITE = OpNames.LOAD_ATTR if _MIN_PY312 else OpNames.LOAD_METHOD +_START_FUNCTION_REWRITE = frozenset(("LOAD_GLOBAL", "LOAD_DEREF")) +_START_BUILTIN_REWRITE = frozenset(("LOAD_GLOBAL",)) +_START_ATTR_REWRITE = frozenset(("LOAD_FAST",)) + + +class RewrittenInstructions: + """ + Standalone class that applies Instruction rewrite/filtering rules. + + This significantly simplifies subsequent parsing by injecting + synthetic POLARS_EXPRESSION ops into the Instruction stream for + easy identification/translation, and separates the parsing logic + from the identification of expression translation opportunities. + """ + + _ignored_ops = frozenset( + [ + "COPY", + "COPY_FREE_VARS", + "NOT_TAKEN", + "POP_TOP", + "PRECALL", + "PUSH_NULL", + "RESUME", + "RETURN_VALUE", + "TO_BOOL", + ] + ) + + def __init__( + self, + instructions: Iterable[Instruction], + function: Callable[[Any], Any], + ) -> None: + self._function = function + self._original_instructions = list(instructions) + + normalised_instructions = [] + + for inst in self._unpack_superinstructions(self._original_instructions): + if inst.opname not in self._ignored_ops: + if inst.opname not in OpNames.MATCHABLE_OPS: + self._rewritten_instructions = [] + return + upgraded_inst = self._update_instruction(inst) + normalised_instructions.append(upgraded_inst) + + self._rewritten_instructions = self._rewrite(normalised_instructions) + + def __len__(self) -> int: + return len(self._rewritten_instructions) + + def __iter__(self) -> Iterator[Instruction]: + return iter(self._rewritten_instructions) + + def __getitem__(self, item: Any) -> Instruction: + return self._rewritten_instructions[item] + + def _matches( + self, + idx: int, + *, + opnames: list[AbstractSet[str]], + argvals: list[AbstractSet[Any] | dict[Any, Any] | None] | None, + is_attr: bool = False, + ) -> list[Instruction]: + """ + Check if a sequence of Instructions matches the specified ops/argvals. + + Parameters + ---------- + idx + The index of the first instruction to check. + opnames + The full opname sequence that defines a match. + argvals + Associated argvals that must also match (in same position as opnames). + is_attr + Indicate if the match represents pure attribute access (cannot be called). + """ + n_required_ops, argvals = len(opnames), argvals or [] + idx_offset = idx + n_required_ops + if ( + is_attr + and (trailing_inst := self._instructions[idx_offset : idx_offset + 1]) + and trailing_inst[0].opname in OpNames.CALL # not pure attr if called + ): + return [] + + instructions = self._instructions[idx:idx_offset] + if len(instructions) == n_required_ops and all( + inst.opname in match_opnames + and (match_argval is None or inst.argval in match_argval) + for inst, match_opnames, match_argval in zip_longest( + instructions, opnames, argvals + ) + ): + return instructions + return [] + + def _rewrite(self, instructions: list[Instruction]) -> list[Instruction]: + """ + Apply rewrite rules, potentially injecting synthetic operations. + + Dispatch rewrite rules by first opcode. Rules operate on the instruction stream + and can examine/modify it as needed, pushing updates into "updated_instructions" + and returning True/False to indicate if any changes were made. + """ + self._instructions = instructions + updated_instructions: list[Instruction] = [] + n = len(instructions) + idx = 0 + + while idx < n: + inst = instructions[idx] + opname = inst.opname + increment = 0 + if opname in _START_FUNCTION_REWRITE: + increment = self._rewrite_functions(idx, updated_instructions) + if not increment and opname in _START_BUILTIN_REWRITE: + increment = self._rewrite_builtins(idx, updated_instructions) + elif opname in _START_METHOD_REWRITE: + increment = self._rewrite_methods(idx, updated_instructions) + elif opname in _START_ATTR_REWRITE: + increment = self._rewrite_attrs(idx, updated_instructions) + + if increment: + idx += increment + else: + updated_instructions.append(inst) + idx += 1 + return updated_instructions + + def _rewrite_attrs(self, idx: int, updated_instructions: list[Instruction]) -> int: + """Replace python attribute lookup with synthetic POLARS_EXPRESSION op.""" + if matching_instructions := self._matches( + idx, + opnames=[_START_ATTR_REWRITE, {"LOAD_ATTR"}], + argvals=[None, _PYTHON_ATTRS_MAP], + is_attr=True, + ): + inst = matching_instructions[1] + expr_name = _PYTHON_ATTRS_MAP[inst.argval] + px = inst._replace( + opname="POLARS_EXPRESSION", argval=expr_name, argrepr=expr_name + ) + updated_instructions.extend([matching_instructions[0], px]) + + return len(matching_instructions) + + def _rewrite_builtins( + self, idx: int, updated_instructions: list[Instruction] + ) -> int: + """Replace builtin function calls with a synthetic POLARS_EXPRESSION op.""" + if matching_instructions := self._matches( + idx, + opnames=[_START_BUILTIN_REWRITE, {"LOAD_FAST", "LOAD_CONST"}, OpNames.CALL], + argvals=[_PYTHON_BUILTINS], + ): + inst1, inst2 = matching_instructions[:2] + if (argval := inst1.argval) in _PYTHON_CASTS_MAP: + dtype = _PYTHON_CASTS_MAP[argval] + argval = f"cast(pl.{dtype})" + + px = inst1._replace( + opname="POLARS_EXPRESSION", + argval=argval, + argrepr=argval, + offset=inst2.offset, + ) + # POLARS_EXPRESSION is mapped as a unary op, so switch instruction order + operand = inst2._replace(offset=inst1.offset) + updated_instructions.extend((operand, px)) + + return len(matching_instructions) + + def _rewrite_functions( + self, idx: int, updated_instructions: list[Instruction] + ) -> int: + """Replace function calls with a synthetic POLARS_EXPRESSION op.""" + for check_globals in (False, True): + for function_kind in _MODULE_FUNCTIONS: + if check_globals and not function_kind.get("check_load_global", True): + return 0 + + opnames: list[AbstractSet[str]] = ( + [ + _START_FUNCTION_REWRITE, + *function_kind["argument_1_opname"], + *function_kind["argument_1_unary_opname"], + *function_kind["argument_2_opname"], + OpNames.CALL, + ] + if check_globals + else [ + _START_FUNCTION_REWRITE, + *function_kind["module_opname"], + *function_kind["attribute_opname"], + *function_kind["argument_1_opname"], + *function_kind["argument_1_unary_opname"], + *function_kind["argument_2_opname"], + OpNames.CALL, + ] + ) + module_aliases = function_kind["module_name"] + if matching_instructions := self._matches( + idx, + opnames=opnames, + argvals=[ + *function_kind["function_name"], + ] + if check_globals + else [ + *function_kind["module_name"], + *function_kind["attribute_name"], + *function_kind["function_name"], + ], + ): + attribute_count = len(function_kind["attribute_name"]) + inst1, inst2, inst3 = matching_instructions[ + attribute_count : 3 + attribute_count + ] + if check_globals: + expr_name = inst1.argval + func = _get_function_variable(self._function, expr_name) + if func is NO_DEFAULT: + continue + else: + # (some callables have no module, eg: method-wrappers) + module_name = getattr(func, "__module__", None) + if not any((module_name in m) for m in module_aliases): + continue + expr_name = _MODULE_FUNC_TO_EXPR_NAME.get( + f"{module_name}.{expr_name}", expr_name + ) + elif inst1.argval == "json": + expr_name = "str.json_decode" + elif inst1.argval == "datetime": + fmt = matching_instructions[attribute_count + 3].argval + expr_name = f'str.to_datetime(format="{fmt}")' + if not self._is_stdlib_datetime( + inst1.argval, + matching_instructions[0].argval, + attribute_count, + ): + # skip these instructions if not stdlib datetime function + return len(matching_instructions) + elif inst1.argval == "math": + expr_name = _MODULE_FUNC_TO_EXPR_NAME.get( + f"math.{inst2.argval}", inst2.argval + ) + else: + expr_name = inst2.argval + + # note: POLARS_EXPRESSION is mapped as unary op, so switch + # instruction order/offsets (for later RPE-type stack walk) + swap_inst = inst2 if check_globals else inst3 + px = inst1._replace( + opname="POLARS_EXPRESSION", + argval=expr_name, + argrepr=expr_name, + offset=swap_inst.offset, + ) + px._from_module = None if check_globals else (inst1.argval or None) # type: ignore[attr-defined] + operand = swap_inst._replace(offset=inst1.offset) + updated_instructions.extend( + ( + operand, + matching_instructions[3 + attribute_count], + px, + ) + if function_kind["argument_1_unary_opname"] + else (operand, px) + ) + return len(matching_instructions) + + return 0 + + def _rewrite_methods( + self, idx: int, updated_instructions: list[Instruction] + ) -> int: + """Replace python method calls with synthetic POLARS_EXPRESSION op.""" + instructions = self._instructions + inst = instructions[idx] + if ( + inst.opname not in _START_METHOD_REWRITE + or inst.argval not in _PYTHON_METHODS_MAP + ): + return 0 + + end = idx + 1 + stop = min(len(instructions), idx + 4) + while end < stop and instructions[end].opname == "LOAD_CONST": + end += 1 + if end == len(instructions) or instructions[end].opname not in OpNames.CALL: + return 0 + + expr = _PYTHON_METHODS_MAP[inst.argval] + param_values = [i.argval for i in instructions[idx + 1 : end]] + if (n_param_values := len(param_values)) == 1: + param_value = param_values[0] + if isinstance(param_value, tuple) and expr in ( + "str.starts_with", + "str.ends_with", + ): + starts, ends = ("^", "") if "starts" in expr else ("", "$") + rx = "|".join(re_escape(v) for v in param_value) + q = '"' if "'" in param_value else "'" + expr = f"str.contains(r{q}{starts}({rx}){ends}{q})" + else: + expr += f"({param_value!r})" + elif n_param_values > 1: + if expr == "str.replace": + if n_param_values == 3: + old, new, count = param_values + expr += f"({old!r},{new!r},n={count},literal=True)" + else: + old, new = param_values + expr = f"str.replace_all({old!r},{new!r},literal=True)" + else: + expr += f"({','.join(repr(v) for v in param_values)})" + + px = inst._replace(opname="POLARS_EXPRESSION", argval=expr, argrepr=expr) + updated_instructions.append(px) + return end - idx + 1 + + @staticmethod + def _unpack_superinstructions( + instructions: list[Instruction], + ) -> Iterator[Instruction]: + """Expand known 'superinstructions' into their component parts.""" + for inst in instructions: + if inst.opname in ( + "LOAD_FAST_LOAD_FAST", + "LOAD_FAST_BORROW_LOAD_FAST_BORROW", + ): + for idx in (0, 1): + yield inst._replace( + opname="LOAD_FAST", + argval=inst.argval[idx], + argrepr=inst.argval[idx], + ) + else: + yield inst + + @staticmethod + def _update_instruction(inst: Instruction) -> Instruction: + """Update/modify specific instructions to simplify multi-version parsing.""" + if _MIN_PY314: + if (opname := inst.opname) in OpNames.SIMPLIFY_SPECIALIZED: + # simplify specialised opcode variants to their more generic form + # (eg: 'LOAD_FAST_BORROW' -> 'LOAD_FAST', etc) + updated_params = {"opname": OpNames.SIMPLIFY_SPECIALIZED[inst.opname]} + if opname == "LOAD_SMALL_INT": + updated_params["argrepr"] = str(inst.argval) + inst = inst._replace(**updated_params) # type: ignore[arg-type] + + elif opname == "BINARY_OP" and inst.argrepr == "[]": + # special case for new 'BINARY_OP ([])'; revert to 'BINARY_SUBSCR' + inst = inst._replace(opname="BINARY_SUBSCR", argrepr="") + + return inst + + def _is_stdlib_datetime( + self, function_name: str, module_name: str, attribute_count: int + ) -> bool: + return ( + attribute_count == 0 + and _get_function_variable(self._function, function_name) + is datetime.datetime + ) or ( + attribute_count == 1 + and _get_function_variable(self._function, module_name) is datetime + ) + + +# raw opcodes that can appear in a rewritable function; if a function's bytecode +# contains any other opcode, `RewrittenInstructions` is guaranteed to bail out +_REWRITABLE_OPCODES: Final = bytes( + dis.opmap[opname] + for opname in ( + OpNames.MATCHABLE_OPS + | RewrittenInstructions._ignored_ops + | {"CACHE", "LOAD_FAST_LOAD_FAST", "LOAD_FAST_BORROW_LOAD_FAST_BORROW"} + ) + if opname in dis.opmap and dis.opmap[opname] < 256 +) + + +def _raw_function_meta(function: Callable[[Any], Any]) -> tuple[str, str]: + """Identify translatable calls that aren't wrapped inside a lambda/function.""" + try: + func_module = function.__class__.__module__ + func_name = function.__name__ + except AttributeError: + return "", "" + + # numpy function calls + if func_module == "numpy" and func_name in _NUMPY_FUNCTIONS: + return "np", f"{func_name}()" + + # python function calls + elif func_module == "builtins": + if func_name in _PYTHON_CASTS_MAP: + return "builtins", f"cast(pl.{_PYTHON_CASTS_MAP[func_name]})" + elif func_name in _MATH_FUNCTIONS: + import math + + if function is getattr(math, func_name): + expr_name = _MODULE_FUNC_TO_EXPR_NAME.get( + f"math.{func_name}", func_name + ) + return "math", f"{expr_name}()" + elif func_name == "loads": + import json # double-check since it is referenced via 'builtins' + + if function is json.loads: + return "json", "str.json_decode()" + + return "", "" + + +def warn_on_inefficient_map( + function: Callable[[Any], Any], + columns: list[str], + map_target: MapTarget, + dtype: PolarsDataType | None = None, +) -> None: + """ + Generate `PolarsInefficientMapWarning` on poor usage of a `map` function. + + Parameters + ---------- + function + The function passed to `map`. + columns + The column name(s) of the original object; in the case of an `Expr` this + will be a list of length 1, containing the expression's root name. + map_target + The target of the `map` call. One of `"expr"`, `"frame"`, or `"series"`. + dtype + The dtype of the original object, if known (eg: for a `Series`). + """ + if map_target == "frame": + msg = "TODO: 'frame' map-function parsing" + raise NotImplementedError(msg) + + # note: we only consider simple functions with a single col/param + col: str = columns and columns[0] # type: ignore[assignment] + if not col and col != "": + return None + + # the parser introspects function bytecode to determine if we can + # rewrite as a (much) more optimal native polars expression instead + if (parser := _BYTECODE_PARSER_CACHE_.get(key := (function, map_target))) is None: + parser = BytecodeParser(function, map_target) + _BYTECODE_PARSER_CACHE_[key] = parser + + if parser.can_attempt_rewrite(): + parser.warn(col, dtype=dtype) + else: + # handle bare numpy/json functions + module, suggestion = _raw_function_meta(function) + if module and suggestion: + target_name = _get_target_name(col, suggestion, map_target) + parser._map_target_name = target_name + fn = function.__name__ + parser.warn( + col, + suggestion_override=f"{target_name}.{suggestion}", + udf_override=fn if module == "builtins" else f"{module}.{fn}", + ) + + +__all__ = ["BytecodeParser", "warn_on_inefficient_map"] diff --git a/hug/lib/python3.12/site-packages/polars/_utils/unstable.py b/hug/lib/python3.12/site-packages/polars/_utils/unstable.py new file mode 100644 index 0000000000000000000000000000000000000000..d692f7fff1052326c93e79df66c73714415df64b --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/unstable.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import inspect +import os +from functools import wraps +from typing import TYPE_CHECKING, TypeVar + +from polars._warnings import issue_warning +from polars.exceptions import UnstableWarning + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import ParamSpec + + from polars._utils.various import IdentityFunction + + P = ParamSpec("P") + T = TypeVar("T") + + +def issue_unstable_warning(message: str | None = None) -> None: + """ + Issue a warning for use of unstable functionality. + + The `warn_unstable` setting must be enabled, otherwise no warning is issued. + + Parameters + ---------- + message + The message associated with the warning. + + See Also + -------- + Config.warn_unstable + """ + warnings_enabled = bool(int(os.environ.get("POLARS_WARN_UNSTABLE", 0))) + if not warnings_enabled: + return + + if message is None: + message = "this functionality is considered unstable." + message += ( + " It may be changed at any point without it being considered a breaking change." + ) + + issue_warning(message, UnstableWarning) + + +def unstable() -> IdentityFunction: + """Decorator to mark a function as unstable.""" + + def decorate(function: Callable[P, T]) -> Callable[P, T]: + @wraps(function) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + issue_unstable_warning(f"`{function.__name__}` is considered unstable.") + return function(*args, **kwargs) + + wrapper.__signature__ = inspect.signature(function) # type: ignore[attr-defined] + return wrapper + + return decorate diff --git a/hug/lib/python3.12/site-packages/polars/_utils/various.py b/hug/lib/python3.12/site-packages/polars/_utils/various.py new file mode 100644 index 0000000000000000000000000000000000000000..9018255fbbffe8384d030466bbc7a16938a1b53e --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/various.py @@ -0,0 +1,756 @@ +from __future__ import annotations + +import inspect +import os +import re +import sys +import warnings +from collections import Counter +from collections.abc import ( + Collection, + Generator, + Iterable, + MappingView, + Sequence, + Sized, +) +from enum import Enum +from io import BytesIO +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Literal, + NoReturn, + TypeVar, + overload, +) + +import polars as pl +from polars import functions as F +from polars._dependencies import _check_for_numpy, import_optional, subprocess +from polars._dependencies import numpy as np +from polars._warnings import find_stacklevel +from polars.datatypes import ( + Boolean, + Date, + Datetime, + Decimal, + Duration, + Int64, + String, + Time, +) +from polars.datatypes.group import FLOAT_DTYPES, INTEGER_DTYPES + +if TYPE_CHECKING: + from collections.abc import ( + Callable, + Iterator, + MutableMapping, + Reversible, + ) + from typing import ParamSpec, Protocol, TypeGuard + + from polars import DataFrame, Expr + from polars._typing import PolarsDataType, SizeUnit + + if sys.version_info >= (3, 13): + from typing import TypeIs + else: + from typing_extensions import TypeIs + + P = ParamSpec("P") + T = TypeVar("T") + + class IdentityFunction(Protocol): + # Use as a return type for signature preserving decorators + def __call__(self, fn: Callable[P, T], /) -> Callable[P, T]: ... + + +# note: reversed views don't match as instances of MappingView +_views: list[Reversible[Any]] = [{}.keys(), {}.values(), {}.items()] +_reverse_mapping_views = tuple(type(reversed(view)) for view in _views) + + +def _process_null_values( + null_values: None | str | Sequence[str] | dict[str, str] = None, +) -> None | str | Sequence[str] | list[tuple[str, str]]: + if isinstance(null_values, dict): + return list(null_values.items()) + else: + return null_values + + +def _is_generator(val: object | Iterator[T]) -> TypeIs[Iterator[T]]: + return ( + isinstance(val, (Generator, Iterable)) and not isinstance(val, Sized) + ) or isinstance(val, (MappingView, *_reverse_mapping_views)) + + +def _is_iterable_of(val: Iterable[object], eltype: type | tuple[type, ...]) -> bool: + """Check whether the given iterable is of the given type(s).""" + return all(isinstance(x, eltype) for x in val) + + +def is_path_or_str_sequence( + val: object, *, allow_str: bool = False, include_series: bool = False +) -> TypeGuard[Sequence[str | Path]]: + """ + Check that `val` is a sequence of strings or paths. + + Note that a single string is a sequence of strings by definition, use + `allow_str=False` to return False on a single string. + """ + if allow_str is False and isinstance(val, str): + return False + elif _check_for_numpy(val) and isinstance(val, np.ndarray): + return np.issubdtype(val.dtype, np.str_) + elif include_series and isinstance(val, pl.Series): + return val.dtype == pl.String + return ( + not isinstance(val, bytes) + and isinstance(val, Sequence) + and _is_iterable_of(val, (Path, str)) + ) + + +def is_bool_sequence( + val: object, *, include_series: bool = False +) -> TypeGuard[Sequence[bool]]: + """Check whether the given sequence is a sequence of booleans.""" + if _check_for_numpy(val) and isinstance(val, np.ndarray): + return val.dtype == np.bool_ + elif include_series and isinstance(val, pl.Series): + return val.dtype == pl.Boolean + return isinstance(val, Sequence) and _is_iterable_of(val, bool) + + +def is_int_sequence( + val: object, *, include_series: bool = False +) -> TypeGuard[Sequence[int]]: + """Check whether the given sequence is a sequence of integers.""" + if _check_for_numpy(val) and isinstance(val, np.ndarray): + return np.issubdtype(val.dtype, np.integer) + elif include_series and isinstance(val, pl.Series): + return val.dtype.is_integer() + return isinstance(val, Sequence) and _is_iterable_of(val, int) + + +def is_sequence( + val: object, *, include_series: bool = False +) -> TypeGuard[Sequence[Any]]: + """Check whether the given input is a numpy array or python sequence.""" + return (_check_for_numpy(val) and isinstance(val, np.ndarray)) or ( + isinstance(val, (pl.Series, Sequence) if include_series else Sequence) + and not isinstance(val, str) + ) + + +def is_non_empty_sequence_of(obj: Sequence[Any], tp: type[T]) -> TypeIs[Sequence[T]]: + # Check if an object is a sequence of `tp`, only sniffing the first element. + return bool( + (first := next(iter(obj), NO_DEFAULT)) is not NO_DEFAULT + and isinstance(first, tp) + ) + + +def is_str_sequence( + val: object, *, allow_str: bool = False, include_series: bool = False +) -> TypeGuard[Sequence[str]]: + """ + Check that `val` is a sequence of strings. + + Note that a single string is a sequence of strings by definition, use + `allow_str=False` to return False on a single string. + """ + if allow_str is False and isinstance(val, str): + return False + elif _check_for_numpy(val) and isinstance(val, np.ndarray): + return np.issubdtype(val.dtype, np.str_) + elif include_series and isinstance(val, pl.Series): + return val.dtype == pl.String + return isinstance(val, Sequence) and _is_iterable_of(val, str) + + +def is_column(obj: Any) -> bool: + """Indicate if the given object is a basic/unaliased column.""" + from polars.expr import Expr + + return isinstance(obj, Expr) and obj.meta.is_column() + + +def warn_null_comparison(obj: Any) -> None: + """Warn for possibly unintentional comparisons with None.""" + if obj is None: + warnings.warn( + "Comparisons with None always result in null. Consider using `.is_null()` or `.is_not_null()`.", + UserWarning, + stacklevel=find_stacklevel(), + ) + + +def range_to_series( + name: str, rng: range, dtype: PolarsDataType | None = None +) -> pl.Series: + """Fast conversion of the given range to a Series.""" + dtype = dtype or Int64 + if dtype.is_integer(): + range = F.int_range( # type: ignore[call-overload] + start=rng.start, end=rng.stop, step=rng.step, dtype=dtype, eager=True + ) + else: + range = F.int_range( + start=rng.start, end=rng.stop, step=rng.step, eager=True + ).cast(dtype) + return range.alias(name) + + +def range_to_slice(rng: range) -> slice: + """Return the given range as an equivalent slice.""" + return slice(rng.start, rng.stop, rng.step) + + +def _in_notebook() -> bool: + try: + from IPython import get_ipython + + if ( + ipy := get_ipython() + ) is not None and "IPKernelApp" not in ipy.config: # pragma: no cover + return False + except ImportError: + return False + except AttributeError: + return False + return True + + +def _in_marimo_notebook() -> bool: + try: + import marimo as mo + + return mo.running_in_notebook() # pragma: no cover + except ImportError: + return False + + +def arrlen(obj: Any) -> int | None: + """Return length of (non-string/dict) sequence; returns None for non-sequences.""" + try: + return None if isinstance(obj, (str, bytes, dict)) else len(obj) + except TypeError: + return None + + +def normalize_filepath(path: str | Path, *, check_not_directory: bool = True) -> str: + """Create a string path, expanding the home directory if present.""" + # don't use pathlib here as it modifies slashes (s3:// -> s3:/) + path = os.path.expanduser(path) # noqa: PTH111 + if ( + check_not_directory + and os.path.exists(path) # noqa: PTH110 + and os.path.isdir(path) # noqa: PTH112 + ): + msg = f"expected a file path; {path!r} is a directory" + raise IsADirectoryError(msg) + return path + + +def parse_version(version: Sequence[str | int]) -> tuple[int, ...]: + """Simple version parser; split into a tuple of ints for comparison.""" + if isinstance(version, str): + version = version.split(".") + # Take the first run of digits in each component: stripping every non-digit + # instead would splice a pre-release suffix onto the number before it, making + # "1.2.3rc1" read as (1, 2, 31), and a component carrying no digit at all + # would raise rather than compare. + parts = (re.search(r"\d+", str(v)) for v in version) + return tuple(int(p.group()) if p is not None else 0 for p in parts) + + +def ordered_unique(values: Sequence[Any]) -> list[Any]: + """Return unique list of sequence values, maintaining their order of appearance.""" + seen: set[Any] = set() + add_ = seen.add + return [v for v in values if not (v in seen or add_(v))] + + +def deduplicate_names(names: Iterable[str]) -> list[str]: + """Ensure name uniqueness by appending a counter to subsequent duplicates.""" + seen: MutableMapping[str, int] = Counter() + deduped = [] + for nm in names: + deduped.append(f"{nm}{seen[nm] - 1}" if nm in seen else nm) + seen[nm] += 1 + return deduped + + +@overload +def scale_bytes(sz: int, unit: SizeUnit) -> int | float: ... + + +@overload +def scale_bytes(sz: Expr, unit: SizeUnit) -> Expr: ... + + +def scale_bytes(sz: int | Expr, unit: SizeUnit) -> int | float | Expr: + """Scale size in bytes to other size units (eg: "kb", "mb", "gb", "tb").""" + if unit in {"b", "bytes"}: + return sz + elif unit in {"kb", "kilobytes"}: + return sz / 1024 + elif unit in {"mb", "megabytes"}: + return sz / 1024**2 + elif unit in {"gb", "gigabytes"}: + return sz / 1024**3 + elif unit in {"tb", "terabytes"}: + return sz / 1024**4 + else: + msg = f"`unit` must be one of {{'b', 'kb', 'mb', 'gb', 'tb'}}, got {unit!r}" + raise ValueError(msg) + + +def _cast_repr_strings_with_schema( + df: DataFrame, schema: dict[str, PolarsDataType | None] +) -> DataFrame: + """ + Utility function to cast table repr/string values into frame-native types. + + Parameters + ---------- + df + Dataframe containing string-repr column data. + schema + DataFrame schema containing the desired end-state types. + + Notes + ----- + Table repr strings are less strict (or different) than equivalent CSV data, so need + special handling; as this function is only used for reprs, parsing is flexible. + """ + tp: PolarsDataType | None + if not df.is_empty(): + for tp in df.schema.values(): + if tp != String: + msg = f"DataFrame should contain only String repr data; found {tp!r}" + raise TypeError(msg) + + special_floats = {"-inf", "+inf", "inf", "nan"} + + # duration string scaling + ns_sec = 1_000_000_000 + duration_scaling = { + "ns": 1, + "us": 1_000, + "µs": 1_000, + "ms": 1_000_000, + "s": ns_sec, + "m": ns_sec * 60, + "h": ns_sec * 60 * 60, + "d": ns_sec * 3_600 * 24, + "w": ns_sec * 3_600 * 24 * 7, + } + + # identify duration units and convert to nanoseconds + def str_duration_(td: str | None) -> int | None: + return ( + None + if td is None + else sum( + int(value) * duration_scaling[unit.strip()] + for value, unit in re.findall(r"([+-]?\d+)(\D+)", td) + ) + ) + + cast_cols = {} + for c, tp in schema.items(): + if tp is not None: + if tp.base_type() == Datetime: + tp_base = Datetime(tp.time_unit) # type: ignore[union-attr] + d = F.col(c).str.replace(r"[A-Z ]+$", "") + cast_cols[c] = ( + F.when(d.str.len_bytes() == 19) + .then(d + ".000000000") + .otherwise(d + "000000000") + .str.slice(0, 29) + .str.strptime(tp_base, "%Y-%m-%d %H:%M:%S.%9f") + ) + if getattr(tp, "time_zone", None) is not None: + cast_cols[c] = cast_cols[c].dt.replace_time_zone(tp.time_zone) # type: ignore[union-attr] + elif tp == Date: + cast_cols[c] = F.col(c).str.strptime(tp, "%Y-%m-%d") # type: ignore[arg-type] + elif tp == Time: + cast_cols[c] = ( + F.when(F.col(c).str.len_bytes() == 8) + .then(F.col(c) + ".000000000") + .otherwise(F.col(c) + "000000000") + .str.slice(0, 18) + .str.strptime(tp, "%H:%M:%S.%9f") # type: ignore[arg-type] + ) + elif tp == Duration: + cast_cols[c] = ( + F.col(c) + .map_elements(str_duration_, return_dtype=Int64) + .cast(Duration("ns")) + .cast(tp) + ) + elif tp == Boolean: + cast_cols[c] = F.col(c).replace_strict({"true": True, "false": False}) + elif tp in INTEGER_DTYPES: + int_string = F.col(c).str.replace_all(r"[^\d+-]", "") + cast_cols[c] = ( + pl.when(int_string.str.len_bytes() > 0).then(int_string).cast(tp) + ) + elif tp in FLOAT_DTYPES or tp.base_type() == Decimal: + # identify integer/fractional parts + integer_part = F.col(c).str.replace(r"^(.*)\D(\d*)$", "$1") + fractional_part = F.col(c).str.replace(r"^(.*)\D(\d*)$", "$2") + cast_cols[c] = ( + # check for empty string, special floats, or integer format + pl.when( + F.col(c).str.contains(r"^[+-]?\d*$") + | F.col(c).str.to_lowercase().is_in(special_floats) + ) + .then(pl.when(F.col(c).str.len_bytes() > 0).then(F.col(c))) + # check for scientific notation + .when(F.col(c).str.contains("[eE]")) + .then(F.col(c).str.replace(r"[^eE\d+-]", ".")) + .otherwise( + # recombine sanitised integer/fractional components + pl.concat_str( + integer_part.str.replace_all(r"[^\d+-]", ""), + fractional_part, + separator=".", + ) + ) + .cast(String) + .cast(tp) + ) + elif tp != df.schema[c]: + cast_cols[c] = F.col(c).cast(tp) + + return df.with_columns(**cast_cols) if cast_cols else df + + +# when building docs (with Sphinx) we need access to the functions +# associated with the namespaces from the class, as we don't have +# an instance; @sphinx_accessor is a @property that allows this. +NS = TypeVar("NS") + + +class sphinx_accessor(property): + def __get__( # type: ignore[override] + self, + instance: Any, + cls: type[NS], + ) -> NS: + try: + return self.fget( # type: ignore[misc] + instance if isinstance(instance, cls) else cls + ) + except (AttributeError, ImportError): + return self # type: ignore[return-value] + + +BUILDING_SPHINX_DOCS = os.getenv("BUILDING_SPHINX_DOCS") + + +class _NoDefault(Enum): + # "borrowed" from + # https://github.com/pandas-dev/pandas/blob/e7859983a814b1823cf26e3b491ae2fa3be47c53/pandas/_libs/lib.pyx#L2736-L2748 + no_default = "NO_DEFAULT" + + def __repr__(self) -> str: + return "" + + +# the "NO_DEFAULT" sentinel should typically be used when one of the valid parameter +# values is None, as otherwise we cannot determine if the caller has set that value. +NO_DEFAULT = _NoDefault.no_default +NoDefault = Literal[_NoDefault.no_default] + + +def _get_stack_locals( + of_type: type | Collection[type] | Callable[[Any], bool] | None = None, + *, + named: str | Collection[str] | None = None, + n_objects: int | None = None, + n_frames: int | None = None, +) -> dict[str, Any]: + """ + Retrieve f_locals from all (or the last 'n') stack frames from the calling location. + + Parameters + ---------- + of_type + Only return objects of this type; can be a single class, tuple of + classes, or a callable that returns True/False if the object being + tested is considered a match. + n_objects + If specified, return only the most recent `n` matching objects. + n_frames + If specified, look at objects in the last `n` stack frames only. + named + If specified, only return objects matching the given name(s). + """ + objects = {} + examined_frames = 0 + + if isinstance(named, str): + named = (named,) + if n_frames is None: + n_frames = sys.maxsize + + if inspect.isfunction(of_type): + matches_type = of_type + else: + if isinstance(of_type, Collection): + of_type = tuple(of_type) + + def matches_type(obj: Any) -> bool: # type: ignore[misc] + return isinstance(obj, of_type) # type: ignore[arg-type] + + if named is not None: + if isinstance(named, str): + named = (named,) + elif not isinstance(named, set): + named = set(named) + + stack_frame = inspect.currentframe() + stack_frame = getattr(stack_frame, "f_back", None) + try: + while stack_frame and examined_frames < n_frames: + local_items = list(stack_frame.f_locals.items()) + for nm, obj in reversed(local_items): + if ( + nm not in objects + and (named is None or nm in named) + and (of_type is None or matches_type(obj)) + ): + objects[nm] = obj + if n_objects is not None and len(objects) >= n_objects: + return objects + + stack_frame = stack_frame.f_back + examined_frames += 1 + finally: + # https://docs.python.org/3/library/inspect.html + # > Though the cycle detector will catch these, destruction of the frames + # > (and local variables) can be made deterministic by removing the cycle + # > in a finally clause. + del stack_frame + + return objects + + +def extend_bool( + value: bool | Sequence[bool], # noqa: FBT001 + n_match: int, + value_name: str, + match_name: str, +) -> Sequence[bool]: + """Ensure the given bool or sequence of bools is the correct length.""" + values = [value] * n_match if isinstance(value, bool) else value + if n_match != len(values): + msg = ( + f"the length of `{value_name}` ({len(values)}) " + f"does not match the length of `{match_name}` ({n_match})" + ) + raise ValueError(msg) + return values + + +def in_terminal_that_supports_colour() -> bool: + """ + Determine (within reason) if we are in an interactive terminal that supports color. + + Note: this is not exhaustive, but it covers a lot (most?) of the common cases. + """ + if hasattr(sys.stdout, "isatty"): + # can enhance as necessary, but this is a reasonable start + return ( + sys.stdout.isatty() + and ( + sys.platform != "win32" + or "ANSICON" in os.environ + or "WT_SESSION" in os.environ + or os.environ.get("TERM_PROGRAM") == "vscode" + or os.environ.get("TERM") == "xterm-256color" + ) + ) or os.environ.get("PYCHARM_HOSTED") == "1" + return False + + +def parse_percentiles( + percentiles: Sequence[float] | float | None, *, inject_median: bool = False +) -> Sequence[float]: + """ + Transforms raw percentiles into our preferred format, adding the 50th percentile. + + Raises a ValueError if the percentile sequence is invalid + (e.g. outside the range [0, 1]) + """ + if isinstance(percentiles, float): + percentiles = [percentiles] + elif percentiles is None: + percentiles = [] + if not all((0 <= p <= 1) for p in percentiles): + msg = "`percentiles` must all be in the range [0, 1]" + raise ValueError(msg) + + sub_50_percentiles = sorted(p for p in percentiles if p < 0.5) + at_or_above_50_percentiles = sorted(p for p in percentiles if p >= 0.5) + + if inject_median and ( + not at_or_above_50_percentiles or at_or_above_50_percentiles[0] != 0.5 + ): + at_or_above_50_percentiles = [0.5, *at_or_above_50_percentiles] + + return [*sub_50_percentiles, *at_or_above_50_percentiles] + + +def re_escape(s: str) -> str: + """Escape a string for use in a Polars (Rust) regex.""" + # note: almost the same as the standard python 're.escape' function, but + # escapes _only_ those metachars with meaning to the rust regex crate + re_rust_metachars = r"\\?()|\[\]{}^$#&~.+*-" + return re.sub(f"([{re_rust_metachars}])", r"\\\1", s) + + +# Don't rename or move. This is used by polars cloud +def display_dot_graph( + *, + dot: str, + show: bool = True, + output_path: str | Path | None = None, + raw_output: bool = False, + figsize: tuple[float, float] = (16.0, 12.0), +) -> str | None: + if raw_output: + # we do not show a graph, nor save a graph to disk + return dot + + output_type = ( + "svg" + if (output_path is not None and str(output_path).endswith(".svg")) + or _in_notebook() + or _in_marimo_notebook() + or "POLARS_DOT_SVG_VIEWER" in os.environ + else "png" + ) + + try: + graph = subprocess.check_output( + ["dot", "-Nshape=box", "-T" + output_type], input=f"{dot}".encode() + ) + except (ImportError, FileNotFoundError): + msg = ( + "the graphviz `dot` binary should be on your PATH." + "(If not installed you can download here: https://graphviz.org/download/)" + ) + raise ImportError(msg) from None + + if output_path: + Path(output_path).write_bytes(graph) + + if not show: + return None + + if _in_notebook(): + from IPython.display import SVG, display + + return display(SVG(graph)) + elif _in_marimo_notebook(): + import marimo as mo + + return mo.Html(f"{graph.decode()}") + else: + if (cmd := os.environ.get("POLARS_DOT_SVG_VIEWER", None)) is not None: + import tempfile + + with tempfile.NamedTemporaryFile(suffix=".svg") as file: + file.write(graph) + file.flush() + cmd = cmd.replace("%file%", file.name) + subprocess.run(cmd, shell=True) + return None + + import_optional( + "matplotlib", + err_prefix="", + err_suffix="should be installed to show graphs", + ) + import matplotlib.image as mpimg + import matplotlib.pyplot as plt + + plt.figure(figsize=figsize) + img = mpimg.imread(BytesIO(graph)) + plt.axis("off") + plt.imshow(img) + plt.show() + return None + + +def qualified_type_name(obj: Any, *, qualify_polars: bool = False) -> str: + """ + Return the module-qualified name of the given object as a string. + + Parameters + ---------- + obj + The object to get the qualified name for. + qualify_polars + If False (default), omit the module path for our own (Polars) objects. + """ + if isinstance(obj, type): + module = obj.__module__ + name = obj.__name__ + else: + module = obj.__class__.__module__ + name = obj.__class__.__name__ + + if ( + not module + or module == "builtins" + or (not qualify_polars and module.startswith("polars.")) + ): + return name + + return f"{module}.{name}" + + +def require_same_type(current: Any, other: Any) -> None: + """ + Raise an error if the two arguments are not of the same type. + + The check will not raise an error if one object is of a subclass of the other. + + Parameters + ---------- + current + The object the type of which is being checked against. + other + An object that has to be of the same type. + """ + if not isinstance(other, type(current)) and not isinstance(current, type(other)): + msg = ( + f"expected `other` to be a {qualified_type_name(current)!r}, " + f"not {qualified_type_name(other)!r}" + ) + raise TypeError(msg) + + +class _NamespaceSuggestMixin: + """Mixin that adds suggestions to AttributeError on namespace typos.""" + + def __getattr__(self, name: str) -> NoReturn: + import difflib + + public = [m for m in dir(type(self)) if not m.startswith("_")] + matches = difflib.get_close_matches(name, public, n=1, cutoff=0.6) + if matches: + msg = f"'{type(self).__name__}' object has no attribute {name!r}. Did you mean: {matches[0]!r}?" + else: + msg = f"'{type(self).__name__}' object has no attribute {name!r}" + raise AttributeError(msg) diff --git a/hug/lib/python3.12/site-packages/polars/_utils/wrap.py b/hug/lib/python3.12/site-packages/polars/_utils/wrap.py new file mode 100644 index 0000000000000000000000000000000000000000..0ad666d070359680d6616ca39076cbdf6f7b13fc --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_utils/wrap.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import polars._reexport as pl + +if TYPE_CHECKING: + from polars import DataFrame, Expr, LazyFrame, Series + from polars._plr import PyDataFrame, PyExpr, PyLazyFrame, PySeries + + +def wrap_df(df: PyDataFrame) -> DataFrame: + return pl.DataFrame._from_pydf(df) + + +def wrap_ldf(ldf: PyLazyFrame) -> LazyFrame: + return pl.LazyFrame._from_pyldf(ldf) + + +def wrap_s(s: PySeries) -> Series: + return pl.Series._from_pyseries(s) + + +def wrap_expr(pyexpr: PyExpr) -> Expr: + return pl.Expr._from_pyexpr(pyexpr) diff --git a/hug/lib/python3.12/site-packages/polars/_warnings.py b/hug/lib/python3.12/site-packages/polars/_warnings.py new file mode 100644 index 0000000000000000000000000000000000000000..e20e4e281674ee636a64c32c10355aa6964fd032 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/_warnings.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +import inspect +import warnings +from pathlib import Path +from typing import Any + +import polars as pl + + +def find_stacklevel() -> int: + """ + Find the first place in the stack that is not inside Polars. + + Taken from: + https://github.com/pandas-dev/pandas/blob/ab89c53f48df67709a533b6a95ce3d911871a0a8/pandas/util/_exceptions.py#L30-L51 + """ + pkg_dir = str(Path(pl.__file__).parent) + + # https://stackoverflow.com/questions/17407119/python-inspect-stack-is-slow + frame = inspect.currentframe() + n = 0 + try: + while frame: + fname = inspect.getfile(frame) + if fname.startswith(pkg_dir) or ( + (qualname := getattr(frame.f_code, "co_qualname", None)) + # ignore @singledispatch wrappers + and qualname.startswith("singledispatch.") + ): + frame = frame.f_back + n += 1 + else: + break + finally: + # https://docs.python.org/3/library/inspect.html + # > Though the cycle detector will catch these, destruction of the frames + # > (and local variables) can be made deterministic by removing the cycle + # > in a 'finally' clause. + del frame + return n + + +def issue_warning(message: str, category: type[Warning], **kwargs: Any) -> None: + """ + Issue a warning. + + Parameters + ---------- + message + The message associated with the warning. + category + The warning category. + **kwargs + Additional arguments for `warnings.warn`. Note that the `stacklevel` is + determined automatically. + """ + warnings.warn( + message=message, category=category, stacklevel=find_stacklevel(), **kwargs + ) + + +# this is called from rust +def _polars_warn(msg: str, category: type[Warning] = UserWarning) -> None: + warnings.warn( + msg, + category=category, + stacklevel=find_stacklevel(), + ) diff --git a/hug/lib/python3.12/site-packages/polars/api.py b/hug/lib/python3.12/site-packages/polars/api.py new file mode 100644 index 0000000000000000000000000000000000000000..38e3536c233b1e33230f1cd6c9ce4f1d3222328b --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/api.py @@ -0,0 +1,376 @@ +from __future__ import annotations + +from inspect import isfunction +from typing import TYPE_CHECKING, Generic, TypeVar +from warnings import warn + +import polars._reexport as pl +from polars._warnings import find_stacklevel + +if TYPE_CHECKING: + from collections.abc import Callable + + from polars import DataFrame, Expr, LazyFrame, Series + + +__all__ = [ + "register_dataframe_namespace", + "register_expr_namespace", + "register_lazyframe_namespace", + "register_series_namespace", +] + +# do not allow override of polars' own namespaces (as registered by '_accessors') +_reserved_namespaces: set[str] = set.union( + *(cls._accessors for cls in (pl.DataFrame, pl.Expr, pl.LazyFrame, pl.Series)) +) + + +NS = TypeVar("NS") + + +class NameSpace(Generic[NS]): + """Establish property-like namespace object for user-defined functionality.""" + + def __init__(self, name: str, namespace: type[NS]) -> None: + self._accessor = name + self._ns = namespace + + def __get__(self, instance: NS | None, cls: type[NS]) -> NS | type[NS]: + if instance is None: + return self._ns + + ns_instance = self._ns(instance) # type: ignore[call-arg] + setattr(instance, self._accessor, ns_instance) + return ns_instance + + +def _create_namespace( + name: str, cls: type[Expr | DataFrame | LazyFrame | Series] +) -> Callable[[type[NS]], type[NS]]: + """Register custom namespace against the underlying Polars class.""" + + def namespace(ns_class: type[NS]) -> type[NS]: + if name in _reserved_namespaces: + msg = f"cannot override reserved namespace {name!r}" + raise AttributeError(msg) + elif (attr := getattr(cls, name, None)) is not None: + if isfunction(attr) or isinstance(attr, property) or name.startswith("_"): + msg = f"cannot override `{cls.__name__}.{name}` with custom namespace {ns_class.__name__!r}" + raise AttributeError(msg) + warn( + f"overriding existing custom namespace {name!r} (on {cls.__name__})", + UserWarning, + stacklevel=find_stacklevel(), + ) + + setattr(cls, name, NameSpace(name, ns_class)) + cls._accessors.add(name) + return ns_class + + return namespace + + +def register_expr_namespace(name: str) -> Callable[[type[NS]], type[NS]]: + """ + Decorator for registering custom functionality with a Polars Expr. + + Parameters + ---------- + name + Name under which the functionality will be accessed. + + See Also + -------- + register_dataframe_namespace : Register functionality on a DataFrame. + register_lazyframe_namespace : Register functionality on a LazyFrame. + register_series_namespace : Register functionality on a Series. + + Examples + -------- + >>> @pl.api.register_expr_namespace("pow_n") + ... class PowersOfN: + ... def __init__(self, expr: pl.Expr) -> None: + ... self._expr = expr + ... + ... def next(self, p: int) -> pl.Expr: + ... return (p ** (self._expr.log(p).ceil()).cast(pl.Int64)).cast(pl.Int64) + ... + ... def previous(self, p: int) -> pl.Expr: + ... return (p ** (self._expr.log(p).floor()).cast(pl.Int64)).cast(pl.Int64) + ... + ... def nearest(self, p: int) -> pl.Expr: + ... return (p ** (self._expr.log(p)).round(0).cast(pl.Int64)).cast(pl.Int64) + >>> + >>> df = pl.DataFrame([1.4, 24.3, 55.0, 64.001], schema=["n"]) + >>> df.select( + ... pl.col("n"), + ... pl.col("n").pow_n.next(p=2).alias("next_pow2"), + ... pl.col("n").pow_n.previous(p=2).alias("prev_pow2"), + ... pl.col("n").pow_n.nearest(p=2).alias("nearest_pow2"), + ... ) + shape: (4, 4) + ┌────────┬───────────┬───────────┬──────────────┐ + │ n ┆ next_pow2 ┆ prev_pow2 ┆ nearest_pow2 │ + │ --- ┆ --- ┆ --- ┆ --- │ + │ f64 ┆ i64 ┆ i64 ┆ i64 │ + ╞════════╪═══════════╪═══════════╪══════════════╡ + │ 1.4 ┆ 2 ┆ 1 ┆ 1 │ + │ 24.3 ┆ 32 ┆ 16 ┆ 32 │ + │ 55.0 ┆ 64 ┆ 32 ┆ 64 │ + │ 64.001 ┆ 128 ┆ 64 ┆ 64 │ + └────────┴───────────┴───────────┴──────────────┘ + """ + return _create_namespace(name, pl.Expr) + + +def register_dataframe_namespace(name: str) -> Callable[[type[NS]], type[NS]]: + """ + Decorator for registering custom functionality with a Polars DataFrame. + + Parameters + ---------- + name + Name under which the functionality will be accessed. + + See Also + -------- + register_expr_namespace : Register functionality on an Expr. + register_lazyframe_namespace : Register functionality on a LazyFrame. + register_series_namespace : Register functionality on a Series. + + Examples + -------- + >>> @pl.api.register_dataframe_namespace("split") + ... class SplitFrame: + ... def __init__(self, df: pl.DataFrame) -> None: + ... self._df = df + ... + ... def by_first_letter_of_column_names(self) -> list[pl.DataFrame]: + ... return [ + ... self._df.select([col for col in self._df.columns if col[0] == f]) + ... for f in dict.fromkeys(col[0] for col in self._df.columns) + ... ] + ... + ... def by_first_letter_of_column_values(self, col: str) -> list[pl.DataFrame]: + ... return [ + ... self._df.filter(pl.col(col).str.starts_with(c)) + ... for c in sorted( + ... set(df.select(pl.col(col).str.slice(0, 1)).to_series()) + ... ) + ... ] + >>> + >>> df = pl.DataFrame( + ... data=[["xx", 2, 3, 4], ["xy", 4, 5, 6], ["yy", 5, 6, 7], ["yz", 6, 7, 8]], + ... schema=["a1", "a2", "b1", "b2"], + ... orient="row", + ... ) + >>> df + shape: (4, 4) + ┌─────┬─────┬─────┬─────┐ + │ a1 ┆ a2 ┆ b1 ┆ b2 │ + │ --- ┆ --- ┆ --- ┆ --- │ + │ str ┆ i64 ┆ i64 ┆ i64 │ + ╞═════╪═════╪═════╪═════╡ + │ xx ┆ 2 ┆ 3 ┆ 4 │ + │ xy ┆ 4 ┆ 5 ┆ 6 │ + │ yy ┆ 5 ┆ 6 ┆ 7 │ + │ yz ┆ 6 ┆ 7 ┆ 8 │ + └─────┴─────┴─────┴─────┘ + >>> df.split.by_first_letter_of_column_names() + [shape: (4, 2) + ┌─────┬─────┐ + │ a1 ┆ a2 │ + │ --- ┆ --- │ + │ str ┆ i64 │ + ╞═════╪═════╡ + │ xx ┆ 2 │ + │ xy ┆ 4 │ + │ yy ┆ 5 │ + │ yz ┆ 6 │ + └─────┴─────┘, + shape: (4, 2) + ┌─────┬─────┐ + │ b1 ┆ b2 │ + │ --- ┆ --- │ + │ i64 ┆ i64 │ + ╞═════╪═════╡ + │ 3 ┆ 4 │ + │ 5 ┆ 6 │ + │ 6 ┆ 7 │ + │ 7 ┆ 8 │ + └─────┴─────┘] + >>> df.split.by_first_letter_of_column_values("a1") + [shape: (2, 4) + ┌─────┬─────┬─────┬─────┐ + │ a1 ┆ a2 ┆ b1 ┆ b2 │ + │ --- ┆ --- ┆ --- ┆ --- │ + │ str ┆ i64 ┆ i64 ┆ i64 │ + ╞═════╪═════╪═════╪═════╡ + │ xx ┆ 2 ┆ 3 ┆ 4 │ + │ xy ┆ 4 ┆ 5 ┆ 6 │ + └─────┴─────┴─────┴─────┘, shape: (2, 4) + ┌─────┬─────┬─────┬─────┐ + │ a1 ┆ a2 ┆ b1 ┆ b2 │ + │ --- ┆ --- ┆ --- ┆ --- │ + │ str ┆ i64 ┆ i64 ┆ i64 │ + ╞═════╪═════╪═════╪═════╡ + │ yy ┆ 5 ┆ 6 ┆ 7 │ + │ yz ┆ 6 ┆ 7 ┆ 8 │ + └─────┴─────┴─────┴─────┘] + """ + return _create_namespace(name, pl.DataFrame) + + +def register_lazyframe_namespace(name: str) -> Callable[[type[NS]], type[NS]]: + """ + Decorator for registering custom functionality with a Polars LazyFrame. + + Parameters + ---------- + name + Name under which the functionality will be accessed. + + See Also + -------- + register_expr_namespace : Register functionality on an Expr. + register_dataframe_namespace : Register functionality on a DataFrame. + register_series_namespace : Register functionality on a Series. + + Examples + -------- + >>> @pl.api.register_lazyframe_namespace("types") + ... class DTypeOperations: + ... def __init__(self, lf: pl.LazyFrame) -> None: + ... self._lf = lf + ... + ... def split_by_column_dtypes(self) -> list[pl.LazyFrame]: + ... return [ + ... self._lf.select(pl.col(tp)) + ... for tp in dict.fromkeys(self._lf.collect_schema().dtypes()) + ... ] + ... + ... def upcast_integer_types(self) -> pl.LazyFrame: + ... return self._lf.with_columns( + ... pl.col(tp).cast(pl.Int64) for tp in (pl.Int8, pl.Int16, pl.Int32) + ... ) + >>> + >>> lf = pl.LazyFrame( + ... data={"a": [1, 2], "b": [3, 4], "c": [5.6, 6.7]}, + ... schema=[("a", pl.Int16), ("b", pl.Int32), ("c", pl.Float32)], + ... ) + >>> lf.collect() + shape: (2, 3) + ┌─────┬─────┬─────┐ + │ a ┆ b ┆ c │ + │ --- ┆ --- ┆ --- │ + │ i16 ┆ i32 ┆ f32 │ + ╞═════╪═════╪═════╡ + │ 1 ┆ 3 ┆ 5.6 │ + │ 2 ┆ 4 ┆ 6.7 │ + └─────┴─────┴─────┘ + >>> lf.types.upcast_integer_types().collect() + shape: (2, 3) + ┌─────┬─────┬─────┐ + │ a ┆ b ┆ c │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ i64 ┆ f32 │ + ╞═════╪═════╪═════╡ + │ 1 ┆ 3 ┆ 5.6 │ + │ 2 ┆ 4 ┆ 6.7 │ + └─────┴─────┴─────┘ + + >>> lf = pl.LazyFrame( + ... data=[["xx", 2, 3, 4], ["xy", 4, 5, 6], ["yy", 5, 6, 7], ["yz", 6, 7, 8]], + ... schema=["a1", "a2", "b1", "b2"], + ... orient="row", + ... ) + >>> lf.collect() + shape: (4, 4) + ┌─────┬─────┬─────┬─────┐ + │ a1 ┆ a2 ┆ b1 ┆ b2 │ + │ --- ┆ --- ┆ --- ┆ --- │ + │ str ┆ i64 ┆ i64 ┆ i64 │ + ╞═════╪═════╪═════╪═════╡ + │ xx ┆ 2 ┆ 3 ┆ 4 │ + │ xy ┆ 4 ┆ 5 ┆ 6 │ + │ yy ┆ 5 ┆ 6 ┆ 7 │ + │ yz ┆ 6 ┆ 7 ┆ 8 │ + └─────┴─────┴─────┴─────┘ + >>> pl.collect_all(lf.types.split_by_column_dtypes()) + [shape: (4, 1) + ┌─────┐ + │ a1 │ + │ --- │ + │ str │ + ╞═════╡ + │ xx │ + │ xy │ + │ yy │ + │ yz │ + └─────┘, shape: (4, 3) + ┌─────┬─────┬─────┐ + │ a2 ┆ b1 ┆ b2 │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ i64 ┆ i64 │ + ╞═════╪═════╪═════╡ + │ 2 ┆ 3 ┆ 4 │ + │ 4 ┆ 5 ┆ 6 │ + │ 5 ┆ 6 ┆ 7 │ + │ 6 ┆ 7 ┆ 8 │ + └─────┴─────┴─────┘] + """ + return _create_namespace(name, pl.LazyFrame) + + +def register_series_namespace(name: str) -> Callable[[type[NS]], type[NS]]: + """ + Decorator for registering custom functionality with a polars Series. + + Parameters + ---------- + name + Name under which the functionality will be accessed. + + See Also + -------- + register_expr_namespace : Register functionality on an Expr. + register_dataframe_namespace : Register functionality on a DataFrame. + register_lazyframe_namespace : Register functionality on a LazyFrame. + + Examples + -------- + >>> @pl.api.register_series_namespace("math") + ... class MathShortcuts: + ... def __init__(self, s: pl.Series) -> None: + ... self._s = s + ... + ... def square(self) -> pl.Series: + ... return self._s * self._s + ... + ... def cube(self) -> pl.Series: + ... return self._s * self._s * self._s + >>> + >>> s = pl.Series("n", [1.5, 31.0, 42.0, 64.5]) + >>> s.math.square().alias("s^2") + shape: (4,) + Series: 's^2' [f64] + [ + 2.25 + 961.0 + 1764.0 + 4160.25 + ] + >>> s = pl.Series("n", [1, 2, 3, 4, 5]) + >>> s.math.cube().alias("s^3") + shape: (5,) + Series: 's^3' [i64] + [ + 1 + 8 + 27 + 64 + 125 + ] + """ + return _create_namespace(name, pl.Series) diff --git a/hug/lib/python3.12/site-packages/polars/catalog/__init__.py b/hug/lib/python3.12/site-packages/polars/catalog/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hug/lib/python3.12/site-packages/polars/catalog/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/catalog/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fea174387cb435d30f70901009b834aae7b021ab Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/catalog/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/catalog/unity/__init__.py b/hug/lib/python3.12/site-packages/polars/catalog/unity/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f8a130ce1f50f75553448647a98f5dd58de65450 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/catalog/unity/__init__.py @@ -0,0 +1,19 @@ +from polars.catalog.unity.client import Catalog +from polars.catalog.unity.models import ( + CatalogInfo, + ColumnInfo, + DataSourceFormat, + NamespaceInfo, + TableInfo, + TableType, +) + +__all__ = [ + "Catalog", + "CatalogInfo", + "ColumnInfo", + "DataSourceFormat", + "NamespaceInfo", + "TableInfo", + "TableType", +] diff --git a/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/__init__.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d954bf22052f4d3b35c3a2edea0a704533017622 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/__init__.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/client.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa121075b106e4168f8dfdab0ce05e66f5940787 Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/client.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/models.cpython-312.pyc b/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..275fee821a01c9611addc075f8e8543fa8f8de9d Binary files /dev/null and b/hug/lib/python3.12/site-packages/polars/catalog/unity/__pycache__/models.cpython-312.pyc differ diff --git a/hug/lib/python3.12/site-packages/polars/catalog/unity/client.py b/hug/lib/python3.12/site-packages/polars/catalog/unity/client.py new file mode 100644 index 0000000000000000000000000000000000000000..4e20cea93a5c848864fa50df78e593ae80ec8400 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/catalog/unity/client.py @@ -0,0 +1,753 @@ +from __future__ import annotations + +import contextlib +import importlib +import importlib.util +import os +import sys +from typing import TYPE_CHECKING, Any, Literal + +from polars._utils.expired import RemovedParameter, removed_parameters +from polars._utils.unstable import issue_unstable_warning +from polars._utils.wrap import wrap_ldf +from polars.catalog.unity.models import ( + CatalogInfo, + ColumnInfo, + NamespaceInfo, + TableInfo, +) + +if TYPE_CHECKING: + from collections.abc import Generator + from datetime import datetime + + import deltalake + + from polars._typing import SchemaDict, StorageOptionsDict + from polars.catalog.unity.models import DataSourceFormat, TableType + from polars.dataframe.frame import DataFrame + from polars.io.cloud import ( + CredentialProviderFunction, + CredentialProviderFunctionReturn, + ) + from polars.io.cloud.credential_provider._builder import CredentialProviderBuilder + from polars.lazyframe import LazyFrame + +with contextlib.suppress(ImportError): + from polars._plr import PyCatalogClient + + PyCatalogClient.init_classes( + catalog_info_cls=CatalogInfo, + namespace_info_cls=NamespaceInfo, + table_info_cls=TableInfo, + column_info_cls=ColumnInfo, + ) + + +class Catalog: + """ + Unity catalog client. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + """ + + def __init__( + self, + workspace_url: str, + *, + bearer_token: str | None = "auto", + require_https: bool = True, + ) -> None: + """ + Initialize a catalog client. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + workspace_url + URL of the workspace, or alternatively the URL of the Unity catalog + API endpoint. + bearer_token + Bearer token to authenticate with. This can also be set to: + + * "auto": Automatically retrieve bearer tokens from the environment. + * "databricks-sdk": Use the Databricks SDK to retrieve and use the + bearer token from the environment. + require_https + Require the `workspace_url` to use HTTPS. + """ + issue_unstable_warning("`Catalog` functionality is considered unstable.") + + if require_https and not workspace_url.startswith("https://"): + msg = ( + f"a non-HTTPS workspace_url was given ({workspace_url}). To " + "allow non-HTTPS URLs, pass require_https=False." + ) + raise ValueError(msg) + + if bearer_token == "databricks-sdk" or ( + bearer_token == "auto" + # For security, in "auto" mode, only retrieve/use the token if: + # * We are running inside a Databricks environment + # * The `workspace_url` is pointing to Databricks and uses HTTPS + and "DATABRICKS_RUNTIME_VERSION" in os.environ + and workspace_url.startswith("https://") + and ( + workspace_url.removeprefix("https://") + .split("/", 1)[0] + .endswith(".cloud.databricks.com") + ) + ): + bearer_token = self._get_databricks_token() + + if bearer_token == "auto": + bearer_token = None + + self._client = PyCatalogClient.new(workspace_url, bearer_token) + + def list_catalogs(self) -> list[CatalogInfo]: + """ + List the available catalogs. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + """ + return self._client.list_catalogs() + + def list_namespaces(self, catalog_name: str) -> list[NamespaceInfo]: + """ + List the available namespaces (unity schema) under the specified catalog. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + """ + return self._client.list_namespaces(catalog_name) + + def list_tables(self, catalog_name: str, namespace: str) -> list[TableInfo]: + """ + List the available tables under the specified schema. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + namespace + Name of the namespace (unity schema). + """ + return self._client.list_tables(catalog_name, namespace) + + def get_table_info( + self, catalog_name: str, namespace: str, table_name: str + ) -> TableInfo: + """ + Retrieve the metadata of the specified table. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + namespace + Name of the namespace (unity schema). + table_name + Name of the table. + """ + return self._client.get_table_info(catalog_name, namespace, table_name) + + def _get_table_credentials( + self, table_id: str, *, write: bool + ) -> tuple[dict[str, str] | None, dict[str, str], int]: + return self._client.get_table_credentials(table_id=table_id, write=write) + + @removed_parameters( + RemovedParameter( + name="retries", + deprecated_in="1.37.1", + removed_in="2.0", + hint='Pass {"max_retries": n} via `storage_options` instead.', + ), + ) + def scan_table( + self, + catalog_name: str, + namespace: str, + table_name: str, + *, + delta_table_version: int | str | datetime | None = None, + delta_table_options: dict[str, Any] | None = None, + storage_options: StorageOptionsDict | None = None, + credential_provider: ( + CredentialProviderFunction | Literal["auto"] | None + ) = "auto", + ) -> LazyFrame: + """ + Retrieve the metadata of the specified table. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + namespace + Name of the namespace (unity schema). + table_name + Name of the table. + delta_table_version + Version of the table to scan (Deltalake only). + delta_table_options + Additional keyword arguments while reading a Deltalake table. + storage_options + Options that indicate how to connect to a cloud provider. + + The cloud providers currently supported are AWS, GCP, and Azure. + See supported keys here: + + * `aws `_ + * `gcp `_ + * `azure `_ + * Hugging Face (`hf://`): Accepts an API key under the `token` parameter: \ + `{'token': '...'}`, or by setting the `HF_TOKEN` environment variable. + + If `storage_options` is not provided, Polars will try to infer the + information from environment variables. + credential_provider + Provide a function that can be called to provide cloud storage + credentials. The function is expected to return a dictionary of + credential keys along with an optional credential expiry time. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + """ + table_info = self.get_table_info(catalog_name, namespace, table_name) + storage_location, data_source_format = _extract_location_and_data_format( + table_info, "scan table" + ) + + credential_provider, storage_options = self._init_credentials( # type: ignore[assignment] + credential_provider, + storage_options, + table_info, + write=False, + caller_name="Catalog.scan_table", + ) + + if data_source_format in ["DELTA", "DELTASHARING"]: + from polars.io.delta import scan_delta + + return scan_delta( + storage_location, + version=delta_table_version, + delta_table_options=delta_table_options, + storage_options=storage_options, + credential_provider=credential_provider, + ) + + if delta_table_version is not None: + msg = ( + "cannot apply delta_table_version for table of type " + f"{data_source_format}" + ) + raise ValueError(msg) + + if delta_table_options is not None: + msg = ( + "cannot apply delta_table_options for table of type " + f"{data_source_format}" + ) + raise ValueError(msg) + + return wrap_ldf( + self._client.scan_table( + catalog_name, + namespace, + table_name, + credential_provider=credential_provider, + cloud_options=storage_options, + ) + ) + + def write_table( + self, + df: DataFrame, + catalog_name: str, + namespace: str, + table_name: str, + *, + delta_mode: Literal[ + "error", "append", "overwrite", "ignore", "merge" + ] = "error", + delta_write_options: dict[str, Any] | None = None, + delta_merge_options: dict[str, Any] | None = None, + storage_options: StorageOptionsDict | None = None, + credential_provider: CredentialProviderFunction + | Literal["auto"] + | None = "auto", + ) -> None | deltalake.table.TableMerger: + """ + Write a DataFrame to a catalog table. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + df + DataFrame to write. + catalog_name + Name of the catalog. + namespace + Name of the namespace (unity schema). + table_name + Name of the table. + delta_mode : {'error', 'append', 'overwrite', 'ignore', 'merge'} + (For delta tables) How to handle existing data. + + - If 'error', throw an error if the table already exists (default). + - If 'append', will add new data. + - If 'overwrite', will replace table with new data. + - If 'ignore', will not write anything if table already exists. + - If 'merge', return a `TableMerger` object to merge data from the DataFrame + with the existing data. + delta_write_options + (For delta tables) Additional keyword arguments while writing a + Delta lake Table. + See a list of supported write options `here `__. + delta_merge_options + (For delta tables) Keyword arguments which are required to `MERGE` a + Delta lake Table. + See a list of supported merge options `here `__. + storage_options + Options that indicate how to connect to a cloud provider. + + The cloud providers currently supported are AWS, GCP, and Azure. + See supported keys here: + + * `aws `_ + * `gcp `_ + * `azure `_ + * Hugging Face (`hf://`): Accepts an API key under the `token` parameter: \ + `{'token': '...'}`, or by setting the `HF_TOKEN` environment variable. + + If `storage_options` is not provided, Polars will try to infer the + information from environment variables. + credential_provider + Provide a function that can be called to provide cloud storage + credentials. The function is expected to return a dictionary of + credential keys along with an optional credential expiry time. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + """ + table_info = self.get_table_info(catalog_name, namespace, table_name) + storage_location, data_source_format = _extract_location_and_data_format( + table_info, "scan table" + ) + + credential_provider, storage_options = self._init_credentials( # type: ignore[assignment] + credential_provider, + storage_options, + table_info, + write=True, + caller_name="Catalog.write_table", + ) + + if data_source_format in ["DELTA", "DELTASHARING"]: + return df.write_delta( # type: ignore[misc] + storage_location, + storage_options=storage_options, + credential_provider=credential_provider, + mode=delta_mode, + delta_write_options=delta_write_options, + delta_merge_options=delta_merge_options, + ) # type: ignore[call-overload] + + else: + msg = ( + "write_table: table format of " + f"{catalog_name}.{namespace}.{table_name} " + f"({data_source_format}) is unsupported." + ) + raise NotImplementedError(msg) + + def create_catalog( + self, + catalog_name: str, + *, + comment: str | None = None, + storage_root: str | None = None, + ) -> CatalogInfo: + """ + Create a catalog. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + comment + Leaves a comment about the catalog. + storage_root + Base location at which to store the catalog. + """ + return self._client.create_catalog( + catalog_name=catalog_name, comment=comment, storage_root=storage_root + ) + + def delete_catalog( + self, + catalog_name: str, + *, + force: bool = False, + ) -> None: + """ + Delete a catalog. + + Note that depending on the table type and catalog server, this may not + delete the actual data files from storage. For more details, please + consult the documentation of the catalog provider you are using. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + force + Forcibly delete the catalog even if it is not empty. + """ + self._client.delete_catalog(catalog_name=catalog_name, force=force) + + def create_namespace( + self, + catalog_name: str, + namespace: str, + *, + comment: str | None = None, + storage_root: str | None = None, + ) -> NamespaceInfo: + """ + Create a namespace (unity schema) in the catalog. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + namespace + Name of the namespace (unity schema). + comment + Leaves a comment about the table. + storage_root + Base location at which to store the namespace. + """ + return self._client.create_namespace( + catalog_name=catalog_name, + namespace=namespace, + comment=comment, + storage_root=storage_root, + ) + + def delete_namespace( + self, + catalog_name: str, + namespace: str, + *, + force: bool = False, + ) -> None: + """ + Delete a namespace (unity schema) in the catalog. + + Note that depending on the table type and catalog server, this may not + delete the actual data files from storage. For more details, please + consult the documentation of the catalog provider you are using. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + namespace + Name of the namespace (unity schema). + force + Forcibly delete the namespace even if it is not empty. + """ + self._client.delete_namespace( + catalog_name=catalog_name, namespace=namespace, force=force + ) + + def create_table( + self, + catalog_name: str, + namespace: str, + table_name: str, + *, + schema: SchemaDict | None, + table_type: TableType, + data_source_format: DataSourceFormat | None = None, + comment: str | None = None, + storage_root: str | None = None, + properties: dict[str, str] | None = None, + ) -> TableInfo: + """ + Create a table in the catalog. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + namespace + Name of the namespace (unity schema). + table_name + Name of the table. + schema + Schema of the table. + table_type + Type of the table + data_source_format + Storage format of the table. + comment + Leaves a comment about the table. + storage_root + Base location at which to store the table. + properties + Extra key-value metadata to store. + """ + return self._client.create_table( + catalog_name=catalog_name, + namespace=namespace, + table_name=table_name, + schema=schema, + table_type=table_type, + data_source_format=data_source_format, + comment=comment, + storage_root=storage_root, + properties=list((properties or {}).items()), + ) + + def delete_table( + self, + catalog_name: str, + namespace: str, + table_name: str, + ) -> None: + """ + Delete the table stored at this location. + + Note that depending on the table type and catalog server, this may not + delete the actual data files from storage. For more details, please + consult the documentation of the catalog provider you are using. + + If you would like to perform manual deletions, the storage location of + the files can be found using `get_table_info`. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + + Parameters + ---------- + catalog_name + Name of the catalog. + namespace + Name of the namespace (unity schema). + table_name + Name of the table. + """ + self._client.delete_table( + catalog_name=catalog_name, + namespace=namespace, + table_name=table_name, + ) + + def _init_credentials( + self, + credential_provider: CredentialProviderFunction | Literal["auto"] | None, + storage_options: StorageOptionsDict | None, + table_info: TableInfo, + *, + write: bool, + caller_name: str, + ) -> tuple[ + CredentialProviderBuilder | None, + dict[str, Any] | None, + ]: + from polars.io.cloud.credential_provider._builder import ( + CredentialProviderBuilder, + ) + from polars.io.cloud.credential_provider._providers import ( + CredentialProviderAzure, + ) + + if credential_provider != "auto": + if credential_provider: + return CredentialProviderBuilder.from_initialized_provider( + credential_provider + ), storage_options + else: + return None, storage_options + + verbose = os.getenv("POLARS_VERBOSE") == "1" + + catalog_credential_provider = CatalogCredentialProvider( + self, table_info.table_id, write=write + ) + + try: + v = catalog_credential_provider._credentials_iter() + storage_update_options = next(v) + + if storage_update_options: + storage_options = {**(storage_options or {}), **storage_update_options} + + if ( + table_info.storage_location is not None + and ( + azure_storage_account_name + := CredentialProviderAzure._extract_adls_uri_storage_account( + table_info.storage_location + ) + ) + is not None + ): + storage_options = storage_options or {} + storage_options["azure_storage_account_name"] = ( + azure_storage_account_name + ) + + for _ in v: + pass + + except Exception as e: + if verbose: + table_name = table_info.name + table_id = table_info.table_id + msg = ( + f"error auto-initializing CatalogCredentialProvider: {e!r} " + f"{table_name = } ({table_id = }) ({write = })" + ) + print(msg, file=sys.stderr) + else: + if verbose: + table_name = table_info.name + table_id = table_info.table_id + msg = ( + "auto-selected CatalogCredentialProvider for " + f"{table_name = } ({table_id = })" + ) + print(msg, file=sys.stderr) + + return CredentialProviderBuilder.from_initialized_provider( + catalog_credential_provider + ), storage_options + + # This should generally not happen, but if using the temporary + # credentials API fails for whatever reason, we fallback to our built-in + # credential provider resolution. + + from polars.io.cloud.credential_provider._builder import ( + _init_credential_provider_builder, + ) + + return _init_credential_provider_builder( + "auto", table_info.storage_location, storage_options, caller_name + ), storage_options + + @classmethod + def _get_databricks_token(cls) -> str: + if importlib.util.find_spec("databricks.sdk") is None: + msg = "could not get Databricks token: databricks-sdk is not installed" + raise ImportError(msg) + + # We code like this to bypass linting + m = importlib.import_module("databricks.sdk.core").__dict__ + + return m["DefaultCredentials"]()(m["Config"]())()["Authorization"][7:] + + +class CatalogCredentialProvider: + """Retrieves credentials from the Unity catalog temporary credentials API.""" + + catalog: Catalog + table_id: str + write: bool + + def __init__(self, catalog: Catalog, table_id: str, *, write: bool) -> None: + self.catalog = catalog + self.table_id = table_id + self.write = write + + def __call__(self) -> CredentialProviderFunctionReturn: # noqa: D102 + _, (creds, expiry) = self._credentials_iter() + return creds, expiry + + def _credentials_iter(self) -> Generator[Any]: + creds, storage_update_options, expiry = self.catalog._get_table_credentials( + self.table_id, write=self.write + ) + + yield storage_update_options + + if not creds: + table_id = self.table_id + msg = ( + "did not receive credentials from temporary credentials API for " + f"{table_id = }" + ) + raise Exception(msg) # noqa: TRY002 + + yield creds, expiry + + +def _extract_location_and_data_format( + table_info: TableInfo, operation: str +) -> tuple[str, DataSourceFormat]: + if table_info.storage_location is None: + msg = f"cannot {operation}: no storage_location found" + raise ValueError(msg) + + if table_info.data_source_format is None: + msg = f"cannot {operation}: no data_source_format found" + raise ValueError(msg) + + return table_info.storage_location, table_info.data_source_format diff --git a/hug/lib/python3.12/site-packages/polars/catalog/unity/models.py b/hug/lib/python3.12/site-packages/polars/catalog/unity/models.py new file mode 100644 index 0000000000000000000000000000000000000000..2d54d29aaed39ea7a38b2e3dc4e9ad093a5b77f8 --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/catalog/unity/models.py @@ -0,0 +1,152 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Literal + +from polars._utils.unstable import issue_unstable_warning +from polars.exceptions import DuplicateError +from polars.schema import Schema + +if TYPE_CHECKING: + from datetime import datetime + + from polars.datatypes.classes import DataType + + +@dataclass +class CatalogInfo: + """Information for a catalog within a metastore.""" + + name: str + comment: str | None + properties: dict[str, str] + options: dict[str, str] + storage_location: str | None + created_at: datetime | None + created_by: str | None + updated_at: datetime | None + updated_by: str | None + + +@dataclass +class NamespaceInfo: + """ + Information for a namespace within a catalog. + + This is also known by the name "schema" in unity catalog terminology. + """ + + name: str + comment: str | None + properties: dict[str, str] + storage_location: str | None + created_at: datetime | None + created_by: str | None + updated_at: datetime | None + updated_by: str | None + + +@dataclass +class TableInfo: + """Information for a catalog table.""" + + name: str + comment: str | None + table_id: str + table_type: TableType + storage_location: str | None + data_source_format: DataSourceFormat | None + columns: list[ColumnInfo] | None + properties: dict[str, str] + created_at: datetime | None + created_by: str | None + updated_at: datetime | None + updated_by: str | None + + def get_polars_schema(self) -> Schema | None: + """ + Get the native polars schema of this table. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + """ + issue_unstable_warning( + "`get_polars_schema` functionality is considered unstable." + ) + if self.columns is None: + return None + + schema = Schema() + + for column_info in self.columns: + if column_info.name in schema: + msg = f"duplicate column name: {column_info.name}" + raise DuplicateError(msg) + schema[column_info.name] = column_info.get_polars_dtype() + + return schema + + +@dataclass +class ColumnInfo: + """Information for a column within a catalog table.""" + + name: str + type_name: str + type_text: str + type_json: str + position: int | None + comment: str | None + partition_index: int | None + + def get_polars_dtype(self) -> DataType: + """ + Get the native polars datatype of this column. + + .. warning:: + This functionality is considered **unstable**. It may be changed + at any point without it being considered a breaking change. + """ + issue_unstable_warning( + "`get_polars_dtype` functionality is considered unstable." + ) + + from polars._plr import PyCatalogClient + + return PyCatalogClient.type_json_to_polars_type(self.type_json) + + +TableType = Literal[ + "MANAGED", + "EXTERNAL", + "VIEW", + "MATERIALIZED_VIEW", + "STREAMING_TABLE", + "MANAGED_SHALLOW_CLONE", + "FOREIGN", + "EXTERNAL_SHALLOW_CLONE", +] + +DataSourceFormat = Literal[ + "DELTA", + "CSV", + "JSON", + "AVRO", + "PARQUET", + "ORC", + "TEXT", + "UNITY_CATALOG", + "DELTASHARING", + "DATABRICKS_FORMAT", + "REDSHIFT_FORMAT", + "SNOWFLAKE_FORMAT", + "SQLDW_FORMAT", + "SALESFORCE_FORMAT", + "BIGQUERY_FORMAT", + "NETSUITE_FORMAT", + "WORKDAY_RAAS_FORMAT", + "HIVE_SERDE", + "HIVE_CUSTOM", + "VECTOR_INDEX_FORMAT", +] diff --git a/hug/lib/python3.12/site-packages/polars/config.py b/hug/lib/python3.12/site-packages/polars/config.py new file mode 100644 index 0000000000000000000000000000000000000000..e15c4f0fa87aabd091e377e83e96db53bc78f54d --- /dev/null +++ b/hug/lib/python3.12/site-packages/polars/config.py @@ -0,0 +1,1741 @@ +from __future__ import annotations + +import contextlib +import os +from pathlib import Path +from typing import TYPE_CHECKING, Any, Final, Literal, TypedDict, get_args + +from polars._dependencies import json +from polars._typing import EngineType +from polars._utils.expired import getattr_fallback, raise_for_removed_attributes +from polars._utils.monitoring import ( + MONITORING_ENV_VAR, + MONITORING_ORGANIZATION_ENV_VAR, + MONITORING_WORKSPACE_ENV_VAR, + activate_monitoring, +) +from polars._utils.unstable import unstable +from polars._utils.various import normalize_filepath +from polars.lazyframe.engine import Engine +from polars.lazyframe.engine_config import ( + SUPPORTED_ENGINE_NAMES, + get_engine_affinity_override, + set_engine_affinity_override, +) + +if TYPE_CHECKING: + from collections.abc import Callable + from types import TracebackType + from typing import Self, TypeAlias, Unpack + + from polars._typing import Alignment, EngineType, FloatFmt + from polars.io.cloud.credential_provider._providers import ( + CredentialProviderFunction, + ) + + +__all__ = ["Config"] + +TableFormatNames: TypeAlias = Literal[ + "ASCII_FULL", + "ASCII_FULL_CONDENSED", + "ASCII_NO_BORDERS", + "ASCII_BORDERS_ONLY", + "ASCII_BORDERS_ONLY_CONDENSED", + "ASCII_HORIZONTAL_ONLY", + "ASCII_MARKDOWN", + "MARKDOWN", + "UTF8_FULL", + "UTF8_FULL_CONDENSED", + "UTF8_NO_BORDERS", + "UTF8_BORDERS_ONLY", + "UTF8_HORIZONTAL_ONLY", + "NOTHING", +] + +# note: register all Config-specific environment variable names here; need to constrain +# which 'POLARS_' environment variables are recognized, as there are other lower-level +# and/or unstable settings that should not be saved or reset with the Config vars. +_POLARS_CFG_ENV_VARS: Final[set[str]] = { + "POLARS_WARN_UNSTABLE", + "POLARS_FMT_MAX_COLS", + "POLARS_FMT_MAX_ROWS", + "POLARS_FMT_NUM_DECIMAL", + "POLARS_FMT_NUM_GROUP_SEPARATOR", + "POLARS_FMT_NUM_LEN", + "POLARS_FMT_STR_LEN", + "POLARS_FMT_TABLE_CELL_ALIGNMENT", + "POLARS_FMT_TABLE_CELL_LIST_LEN", + "POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT", + "POLARS_FMT_TABLE_DATAFRAME_SHAPE_BELOW", + "POLARS_FMT_TABLE_FORMATTING", + "POLARS_FMT_TABLE_HIDE_COLUMN_DATA_TYPES", + "POLARS_FMT_TABLE_HIDE_COLUMN_NAMES", + "POLARS_FMT_TABLE_HIDE_COLUMN_SEPARATOR", + "POLARS_FMT_TABLE_HIDE_DATAFRAME_SHAPE_INFORMATION", + "POLARS_FMT_TABLE_INLINE_COLUMN_DATA_TYPE", + "POLARS_FMT_TABLE_ROUNDED_CORNERS", + "POLARS_IDEAL_MORSEL_SIZE", + "POLARS_TABLE_WIDTH", + "POLARS_VERBOSE", + "POLARS_MAX_EXPR_DEPTH", + "POLARS_ENGINE_AFFINITY", + "POLARS_QUERY_MONITORING", + "POLARS_QUERY_MONITORING_WORKSPACE", + "POLARS_QUERY_MONITORING_ORGANIZATION", +} + +# vars that set the rust env directly should declare themselves here as the Config +# method name paired with a callable that returns the current state of that value: +with contextlib.suppress(ImportError, NameError): + # note: 'plr' not available when building docs + import polars._plr as plr + + _POLARS_CFG_DIRECT_VARS = { + "set_fmt_float": plr.get_float_fmt, + "set_float_precision": plr.get_float_precision, + "set_thousands_separator": plr.get_thousands_separator, + "set_decimal_separator": plr.get_decimal_separator, + "set_trim_decimal_zeros": plr.get_trim_decimal_zeros, + } + + +def _get_default_credential_provider() -> Any: + import polars.io.cloud.credential_provider._builder as builder + + return builder.DEFAULT_CREDENTIAL_PROVIDER + + +def _set_default_credential_provider(provider: Any) -> None: + import polars.io.cloud.credential_provider._builder as builder + + builder.DEFAULT_CREDENTIAL_PROVIDER = provider + + +# Python-object options cannot be stored in environment variables or `Config.save`. +# Register them here so contexts and resets handle them like other options. +_POLARS_CFG_RUNTIME_VARS: Final[ + dict[str, tuple[Callable[[], Any], Callable[[Any], None], Any]] +] = { + "set_engine_affinity": ( + get_engine_affinity_override, + set_engine_affinity_override, + None, + ), + "set_default_credential_provider": ( + _get_default_credential_provider, + _set_default_credential_provider, + "auto", + ), +} + + +def _snapshot_runtime_vars() -> dict[str, Any]: + """Capture the current value of every runtime-only option, by reference.""" + return {name: get() for name, (get, _, _) in _POLARS_CFG_RUNTIME_VARS.items()} + + +def _restore_runtime_vars(snapshot: dict[str, Any]) -> None: + """Restore runtime-only options from a `_snapshot_runtime_vars` result.""" + for name, (_, set_, default) in _POLARS_CFG_RUNTIME_VARS.items(): + set_(snapshot.get(name, default)) + + +class ConfigParameters(TypedDict, total=False): + """Parameters supported by the polars Config.""" + + ascii_tables: bool | None + decimal_separator: str | None + thousands_separator: str | bool | None + float_precision: int | None + fmt_float: FloatFmt | None + fmt_str_lengths: int | None + fmt_table_cell_list_len: int | None + streaming_chunk_size: int | None + tbl_cell_alignment: Alignment | None + tbl_cell_numeric_alignment: Alignment | None + tbl_cols: int | None + tbl_column_data_type_inline: bool | None + tbl_dataframe_shape_below: bool | None + tbl_formatting: TableFormatNames | None + tbl_hide_column_data_types: bool | None + tbl_hide_column_names: bool | None + tbl_hide_dtype_separator: bool | None + tbl_hide_dataframe_shape: bool | None + tbl_rows: int | None + tbl_width_chars: int | None + trim_decimal_zeros: bool | None + verbose: bool | None + expr_depth_warning: int + engine_affinity: EngineType | None + default_credential_provider: CredentialProviderFunction | Literal["auto"] | None + enable_monitoring: bool | None + + set_ascii_tables: bool | None + set_decimal_separator: str | None + set_thousands_separator: str | bool | None + set_float_precision: int | None + set_fmt_float: FloatFmt | None + set_fmt_str_lengths: int | None + set_fmt_table_cell_list_len: int | None + set_streaming_chunk_size: int | None + set_tbl_cell_alignment: Alignment | None + set_tbl_cell_numeric_alignment: Alignment | None + set_tbl_cols: int | None + set_tbl_column_data_type_inline: bool | None + set_tbl_dataframe_shape_below: bool | None + set_tbl_formatting: TableFormatNames | None + set_tbl_hide_column_data_types: bool | None + set_tbl_hide_column_names: bool | None + set_tbl_hide_dtype_separator: bool | None + set_tbl_hide_dataframe_shape: bool | None + set_tbl_rows: int | None + set_tbl_width_chars: int | None + set_trim_decimal_zeros: bool | None + set_verbose: bool | None + set_expr_depth_warning: int + set_engine_affinity: EngineType | None + + +class _Meta(type): + if not TYPE_CHECKING: + + def __getattr__(cls, name: str) -> Any: + raise_for_removed_attributes( + cls, + name, + {"set_auto_structify": None}, + version="2.0", + ) + return getattr_fallback(cls, super(), name, meta=True) + + +class Config(contextlib.ContextDecorator, metaclass=_Meta): + """ + Configure polars; offers options for table formatting and more. + + Notes + ----- + Can also be used as a context manager OR a function decorator in order to + temporarily scope the lifetime of specific options. For example: + + >>> with pl.Config() as cfg: + ... # set verbose for more detailed output within the scope + ... cfg.set_verbose(True) # doctest: +IGNORE_RESULT + >>> # scope exit - no longer in verbose mode + + This can also be written more compactly as: + + >>> with pl.Config(verbose=True): + ... pass + + (The compact format is available for all `Config` methods that take a single value). + + Alternatively, you can use as a decorator in order to scope the duration of the + selected options to a specific function: + + >>> @pl.Config(verbose=True, apply_on_context_enter=True) + ... def test(): + ... pass + """ + + _context_options: ConfigParameters | None = None + _original_state: str = "" + # `save()` cannot represent runtime-only options, so they are snapshotted + # separately. + _original_runtime_state: dict[str, Any] | None = None + + def __init__( + self, + *, + restore_defaults: bool = False, + apply_on_context_enter: bool = False, + **options: Unpack[ConfigParameters], + ) -> None: + """ + Initialise a Config object instance for context manager usage. + + Any `options` kwargs should correspond to the available named "set_*" + methods, but are allowed to omit the "set_" prefix for brevity. + + Parameters + ---------- + restore_defaults + set all options to their default values (this is applied before + setting any other options). + apply_on_context_enter + defer applying the options until a context is entered. This allows you + to create multiple `Config` instances with different options, and then + reuse them independently as context managers or function decorators + with specific bundles of parameters. + **options + keyword args that will set the option; equivalent to calling the + named "set_